diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-08-24 12:15:48 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-08-28 13:30:04 +0000 |
commit | b014812705fc80bff0a5c120dfcef88f349816dc (patch) | |
tree | 25a2e2d9fa285f1add86aa333389a839f81a39ae /chromium/net/http/http_stream_factory_job_controller_unittest.cc | |
parent | 9f4560b1027ae06fdb497023cdcaf91b8511fa74 (diff) | |
download | qtwebengine-chromium-b014812705fc80bff0a5c120dfcef88f349816dc.tar.gz |
BASELINE: Update Chromium to 68.0.3440.125
Change-Id: I23f19369e01f688e496f5bf179abb521ad73874f
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/net/http/http_stream_factory_job_controller_unittest.cc')
-rw-r--r-- | chromium/net/http/http_stream_factory_job_controller_unittest.cc | 2648 |
1 files changed, 2648 insertions, 0 deletions
diff --git a/chromium/net/http/http_stream_factory_job_controller_unittest.cc b/chromium/net/http/http_stream_factory_job_controller_unittest.cc new file mode 100644 index 00000000000..b499032ed80 --- /dev/null +++ b/chromium/net/http/http_stream_factory_job_controller_unittest.cc @@ -0,0 +1,2648 @@ +// Copyright (c) 2016 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/http/http_stream_factory_job_controller.h" + +#include <algorithm> +#include <list> +#include <string> +#include <utility> +#include <vector> + +#include "base/memory/ptr_util.h" +#include "base/memory/scoped_refptr.h" +#include "base/run_loop.h" +#include "base/strings/stringprintf.h" +#include "base/test/histogram_tester.h" +#include "base/test/scoped_feature_list.h" +#include "base/test/scoped_task_environment.h" +#include "base/test/test_mock_time_task_runner.h" +#include "base/threading/platform_thread.h" +#include "net/base/test_proxy_delegate.h" +#include "net/dns/mock_host_resolver.h" +#include "net/http/http_basic_stream.h" +#include "net/http/http_network_session_peer.h" +#include "net/http/http_stream_factory.h" +#include "net/http/http_stream_factory_job.h" +#include "net/http/http_stream_factory_test_util.h" +#include "net/log/net_log_with_source.h" +#include "net/log/test_net_log.h" +#include "net/log/test_net_log_entry.h" +#include "net/log/test_net_log_util.h" +#include "net/proxy_resolution/mock_proxy_resolver.h" +#include "net/proxy_resolution/proxy_config_service_fixed.h" +#include "net/proxy_resolution/proxy_info.h" +#include "net/proxy_resolution/proxy_resolution_service.h" +#include "net/quic/chromium/mock_crypto_client_stream_factory.h" +#include "net/quic/chromium/mock_quic_data.h" +#include "net/quic/chromium/quic_stream_factory.h" +#include "net/quic/chromium/quic_stream_factory_peer.h" +#include "net/quic/chromium/quic_test_packet_maker.h" +#include "net/socket/socket_test_util.h" +#include "net/spdy/spdy_test_util_common.h" +#include "net/test/test_with_scoped_task_environment.h" +#include "net/third_party/quic/test_tools/mock_random.h" +#include "net/traffic_annotation/network_traffic_annotation_test_helper.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gmock_mutant.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::Contains; +using ::testing::ElementsAre; +using ::testing::Invoke; +using ::testing::IsEmpty; +using ::testing::Key; +using ::testing::SizeIs; + +namespace net { + +namespace test { + +namespace { + +const char kServerHostname[] = "www.example.com"; + +// List of errors for which fallback is expected on an HTTPS proxy. +const int proxy_test_mock_errors[] = { + ERR_PROXY_CONNECTION_FAILED, + ERR_NAME_NOT_RESOLVED, + ERR_ADDRESS_UNREACHABLE, + ERR_CONNECTION_CLOSED, + ERR_CONNECTION_TIMED_OUT, + ERR_CONNECTION_RESET, + ERR_CONNECTION_REFUSED, + ERR_CONNECTION_ABORTED, + ERR_TIMED_OUT, + ERR_SOCKS_CONNECTION_FAILED, + ERR_PROXY_CERTIFICATE_INVALID, + ERR_SSL_PROTOCOL_ERROR, +}; + +class FailingProxyResolverFactory : public ProxyResolverFactory { + public: + FailingProxyResolverFactory() : ProxyResolverFactory(false) {} + + // ProxyResolverFactory override. + int CreateProxyResolver(const scoped_refptr<PacFileData>& script_data, + std::unique_ptr<ProxyResolver>* result, + const CompletionCallback& callback, + std::unique_ptr<Request>* request) override { + return ERR_PAC_SCRIPT_FAILED; + } +}; + +class FailingHostResolver : public MockHostResolverBase { + public: + FailingHostResolver() : MockHostResolverBase(false /*use_caching*/) {} + ~FailingHostResolver() override = default; + + int Resolve(const RequestInfo& info, + RequestPriority priority, + AddressList* addresses, + const CompletionCallback& callback, + std::unique_ptr<Request>* out_req, + const NetLogWithSource& net_log) override { + return ERR_NAME_NOT_RESOLVED; + } +}; + +// TODO(xunjieli): This should just use HangingHostResolver from +// mock_host_resolver.h +class HangingResolver : public MockHostResolverBase { + public: + HangingResolver() : MockHostResolverBase(false /*use_caching*/) {} + ~HangingResolver() override = default; + + int Resolve(const RequestInfo& info, + RequestPriority priority, + AddressList* addresses, + const CompletionCallback& callback, + std::unique_ptr<Request>* out_req, + const NetLogWithSource& net_log) override { + return ERR_IO_PENDING; + } +}; + +// A mock HttpServerProperties that always returns false for IsInitialized(). +class MockHttpServerProperties : public HttpServerPropertiesImpl { + public: + MockHttpServerProperties() = default; + ~MockHttpServerProperties() override = default; + bool IsInitialized() const override { return false; } +}; + +} // anonymous namespace + +class HttpStreamFactoryJobPeer { + public: + static void Start(HttpStreamFactory::Job* job, + HttpStreamRequest::StreamType stream_type) { + // Start() is mocked for MockHttpStreamFactoryJob. + // This is the alternative method to invoke real Start() method on Job. + job->stream_type_ = stream_type; + job->StartInternal(); + } + + // Returns |num_streams_| of |job|. It should be 0 for non-preconnect Jobs. + static int GetNumStreams(const HttpStreamFactory::Job* job) { + return job->num_streams_; + } + + // Return SpdySessionKey of |job|. + static const SpdySessionKey GetSpdySessionKey( + const HttpStreamFactory::Job* job) { + return job->spdy_session_key_; + } + + static void SetShouldReconsiderProxy(HttpStreamFactory::Job* job) { + job->should_reconsider_proxy_ = true; + } + + static void SetStream(HttpStreamFactory::Job* job, + std::unique_ptr<HttpStream> http_stream) { + job->stream_ = std::move(http_stream); + } +}; + +class JobControllerPeer { + public: + static bool main_job_is_blocked( + HttpStreamFactory::JobController* job_controller) { + return job_controller->main_job_is_blocked_; + } + + static bool main_job_is_resumed( + HttpStreamFactory::JobController* job_controller) { + return job_controller->main_job_is_resumed_; + } + + static AlternativeServiceInfo GetAlternativeServiceInfoFor( + HttpStreamFactory::JobController* job_controller, + const HttpRequestInfo& request_info, + HttpStreamRequest::Delegate* delegate, + HttpStreamRequest::StreamType stream_type) { + return job_controller->GetAlternativeServiceInfoFor(request_info, delegate, + stream_type); + } +}; + +class HttpStreamFactoryJobControllerTest + : public TestWithScopedTaskEnvironment { + public: + HttpStreamFactoryJobControllerTest() + : TestWithScopedTaskEnvironment( + base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME) { + session_deps_.enable_quic = true; + } + + void UseAlternativeProxy() { + ASSERT_FALSE(test_proxy_delegate_); + use_alternative_proxy_ = true; + } + + void SetPreconnect() { + ASSERT_FALSE(test_proxy_delegate_); + is_preconnect_ = true; + } + + void DisableIPBasedPooling() { + ASSERT_FALSE(test_proxy_delegate_); + enable_ip_based_pooling_ = false; + } + + void DisableAlternativeServices() { + ASSERT_FALSE(test_proxy_delegate_); + enable_alternative_services_ = false; + } + + void SkipCreatingJobController() { + ASSERT_FALSE(job_controller_); + create_job_controller_ = false; + } + + void Initialize(const HttpRequestInfo& request_info) { + ASSERT_FALSE(test_proxy_delegate_); + auto test_proxy_delegate = std::make_unique<TestProxyDelegate>(); + test_proxy_delegate_ = test_proxy_delegate.get(); + + test_proxy_delegate->set_alternative_proxy_server( + ProxyServer::FromPacString("QUIC myproxy.org:443")); + EXPECT_TRUE(test_proxy_delegate->alternative_proxy_server().is_quic()); + session_deps_.proxy_delegate = std::move(test_proxy_delegate); + + if (quic_data_) + quic_data_->AddSocketDataToFactory(session_deps_.socket_factory.get()); + if (tcp_data_) + session_deps_.socket_factory->AddSocketDataProvider(tcp_data_.get()); + + if (use_alternative_proxy_) { + std::unique_ptr<ProxyResolutionService> proxy_resolution_service = + ProxyResolutionService::CreateFixedFromPacResult( + "HTTPS myproxy.org:443", TRAFFIC_ANNOTATION_FOR_TESTS); + session_deps_.proxy_resolution_service = + std::move(proxy_resolution_service); + } + session_deps_.net_log = net_log_.bound().net_log(); + HttpNetworkSession::Params params = + SpdySessionDependencies::CreateSessionParams(&session_deps_); + HttpNetworkSession::Context session_context = + SpdySessionDependencies::CreateSessionContext(&session_deps_); + + session_context.quic_crypto_client_stream_factory = + &crypto_client_stream_factory_; + session_context.quic_random = &random_generator_; + session_ = std::make_unique<HttpNetworkSession>(params, session_context); + factory_ = static_cast<HttpStreamFactory*>(session_->http_stream_factory()); + if (create_job_controller_) { + job_controller_ = new HttpStreamFactory::JobController( + factory_, &request_delegate_, session_.get(), &job_factory_, + request_info, is_preconnect_, false /* is_websocket */, + enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), + SSLConfig()); + HttpStreamFactoryPeer::AddJobController(factory_, job_controller_); + } + } + + TestProxyDelegate* test_proxy_delegate() const { + return test_proxy_delegate_; + } + + ~HttpStreamFactoryJobControllerTest() override { + if (quic_data_) { + EXPECT_TRUE(quic_data_->AllReadDataConsumed()); + EXPECT_TRUE(quic_data_->AllWriteDataConsumed()); + } + if (tcp_data_) { + EXPECT_TRUE(tcp_data_->AllReadDataConsumed()); + EXPECT_TRUE(tcp_data_->AllWriteDataConsumed()); + } + } + + void SetAlternativeService(const HttpRequestInfo& request_info, + AlternativeService alternative_service) { + url::SchemeHostPort server(request_info.url); + base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1); + if (alternative_service.protocol == kProtoQUIC) { + session_->http_server_properties()->SetQuicAlternativeService( + server, alternative_service, expiration, + session_->params().quic_supported_versions); + } else { + session_->http_server_properties()->SetHttp2AlternativeService( + server, alternative_service, expiration); + } + } + + void VerifyBrokenAlternateProtocolMapping(const HttpRequestInfo& request_info, + bool should_mark_broken) { + const url::SchemeHostPort server(request_info.url); + const AlternativeServiceInfoVector alternative_service_info_vector = + session_->http_server_properties()->GetAlternativeServiceInfos(server); + EXPECT_EQ(1u, alternative_service_info_vector.size()); + EXPECT_EQ(should_mark_broken, + session_->http_server_properties()->IsAlternativeServiceBroken( + alternative_service_info_vector[0].alternative_service())); + } + + TestJobFactory job_factory_; + MockHttpStreamRequestDelegate request_delegate_; + SpdySessionDependencies session_deps_{ProxyResolutionService::CreateDirect()}; + std::unique_ptr<HttpNetworkSession> session_; + HttpStreamFactory* factory_ = nullptr; + HttpStreamFactory::JobController* job_controller_ = nullptr; + std::unique_ptr<HttpStreamRequest> request_; + std::unique_ptr<SequencedSocketData> tcp_data_; + std::unique_ptr<MockQuicData> quic_data_; + MockCryptoClientStreamFactory crypto_client_stream_factory_; + MockClock clock_; + MockRandom random_generator_{0}; + QuicTestPacketMaker client_maker_{ + HttpNetworkSession::Params().quic_supported_versions[0], + 0, + &clock_, + kServerHostname, + Perspective::IS_CLIENT, + false}; + + protected: + BoundTestNetLog net_log_; + bool use_alternative_proxy_ = false; + bool is_preconnect_ = false; + bool enable_ip_based_pooling_ = true; + bool enable_alternative_services_ = true; + + private: + // Not owned by |this|. + TestProxyDelegate* test_proxy_delegate_ = nullptr; + bool create_job_controller_ = true; + + DISALLOW_COPY_AND_ASSIGN(HttpStreamFactoryJobControllerTest); +}; + +TEST_F(HttpStreamFactoryJobControllerTest, ProxyResolutionFailsSync) { + ProxyConfig proxy_config; + proxy_config.set_pac_url(GURL("http://fooproxyurl")); + proxy_config.set_pac_mandatory(true); + session_deps_.proxy_resolution_service.reset(new ProxyResolutionService( + std::make_unique<ProxyConfigServiceFixed>(ProxyConfigWithAnnotation( + proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS)), + std::make_unique<FailingProxyResolverFactory>(), nullptr)); + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("http://www.google.com"); + + Initialize(request_info); + + EXPECT_CALL(request_delegate_, + OnStreamFailed(ERR_MANDATORY_PROXY_CONFIGURATION_FAILED, _, _)) + .Times(1); + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + + EXPECT_FALSE(job_controller_->main_job()); + EXPECT_FALSE(job_controller_->alternative_job()); + + // Make sure calling GetLoadState() when before job creation does not crash. + // Regression test for crbug.com/723920. + EXPECT_EQ(LOAD_STATE_IDLE, job_controller_->GetLoadState()); + + base::RunLoop().RunUntilIdle(); + request_.reset(); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +TEST_F(HttpStreamFactoryJobControllerTest, ProxyResolutionFailsAsync) { + ProxyConfig proxy_config; + proxy_config.set_pac_url(GURL("http://fooproxyurl")); + proxy_config.set_pac_mandatory(true); + MockAsyncProxyResolverFactory* proxy_resolver_factory = + new MockAsyncProxyResolverFactory(false); + MockAsyncProxyResolver resolver; + session_deps_.proxy_resolution_service.reset(new ProxyResolutionService( + std::make_unique<ProxyConfigServiceFixed>(ProxyConfigWithAnnotation( + proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS)), + base::WrapUnique(proxy_resolver_factory), nullptr)); + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("http://www.google.com"); + + Initialize(request_info); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + + EXPECT_FALSE(job_controller_->main_job()); + EXPECT_FALSE(job_controller_->alternative_job()); + + EXPECT_EQ(LOAD_STATE_RESOLVING_PROXY_FOR_URL, + job_controller_->GetLoadState()); + + EXPECT_CALL(request_delegate_, + OnStreamFailed(ERR_MANDATORY_PROXY_CONFIGURATION_FAILED, _, _)) + .Times(1); + proxy_resolver_factory->pending_requests()[0]->CompleteNowWithForwarder( + ERR_FAILED, &resolver); + base::RunLoop().RunUntilIdle(); + request_.reset(); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +TEST_F(HttpStreamFactoryJobControllerTest, NoSupportedProxies) { + session_deps_.proxy_resolution_service = + ProxyResolutionService::CreateFixedFromPacResult( + "QUIC myproxy.org:443", TRAFFIC_ANNOTATION_FOR_TESTS); + session_deps_.enable_quic = false; + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("http://www.google.com"); + + Initialize(request_info); + + EXPECT_CALL(request_delegate_, OnStreamFailed(ERR_NO_SUPPORTED_PROXIES, _, _)) + .Times(1); + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + + EXPECT_FALSE(job_controller_->main_job()); + EXPECT_FALSE(job_controller_->alternative_job()); + + base::RunLoop().RunUntilIdle(); + request_.reset(); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +class JobControllerReconsiderProxyAfterErrorTest + : public HttpStreamFactoryJobControllerTest, + public ::testing::WithParamInterface<::testing::tuple<bool, int>> { + public: + void Initialize( + std::unique_ptr<ProxyResolutionService> proxy_resolution_service, + std::unique_ptr<ProxyDelegate> proxy_delegate) { + session_deps_.proxy_delegate = std::move(proxy_delegate); + session_deps_.proxy_resolution_service = + std::move(proxy_resolution_service); + session_ = std::make_unique<HttpNetworkSession>( + SpdySessionDependencies::CreateSessionParams(&session_deps_), + SpdySessionDependencies::CreateSessionContext(&session_deps_)); + factory_ = session_->http_stream_factory(); + } + + std::unique_ptr<HttpStreamRequest> CreateJobController( + const HttpRequestInfo& request_info) { + HttpStreamFactory::JobController* job_controller = + new HttpStreamFactory::JobController( + factory_, &request_delegate_, session_.get(), &default_job_factory_, + request_info, is_preconnect_, false /* is_websocket */, + enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), + SSLConfig()); + HttpStreamFactoryPeer::AddJobController(factory_, job_controller); + return job_controller->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, + DEFAULT_PRIORITY); + } + + private: + // Use real Jobs so that Job::Resume() is not mocked out. When main job is + // resumed it will use mock socket data. + HttpStreamFactory::JobFactory default_job_factory_; +}; + +INSTANTIATE_TEST_CASE_P( + /* no prefix */, + JobControllerReconsiderProxyAfterErrorTest, + ::testing::Combine(::testing::Bool(), + testing::ValuesIn(proxy_test_mock_errors))); + +// TODO(eroman): The testing should be expanded to test cases where proxy +// fallback is NOT supposed to occur, and also vary across all of +// the proxy types. +TEST_P(JobControllerReconsiderProxyAfterErrorTest, ReconsiderProxyAfterError) { + // Use mock proxy client sockets to test the fallback behavior of error codes + // returned by HttpProxyClientSocketWrapper. Errors returned by transport + // sockets usually get re-written by the wrapper class. crbug.com/826570. + session_deps_.socket_factory->UseMockProxyClientSockets(); + + const bool set_alternative_proxy_server = ::testing::get<0>(GetParam()); + const int mock_error = ::testing::get<1>(GetParam()); + std::unique_ptr<ProxyResolutionService> proxy_resolution_service = + ProxyResolutionService::CreateFixedFromPacResult( + "HTTPS badproxy:99; HTTPS badfallbackproxy:98; DIRECT", + TRAFFIC_ANNOTATION_FOR_TESTS); + auto test_proxy_delegate = std::make_unique<TestProxyDelegate>(); + TestProxyDelegate* test_proxy_delegate_raw = test_proxy_delegate.get(); + + // Before starting the test, verify that there are no proxies marked as bad. + ASSERT_TRUE(proxy_resolution_service->proxy_retry_info().empty()) + << mock_error; + + // Alternative Proxy job is given preference over the main job, so populate + // the socket provider first. + StaticSocketDataProvider socket_data_proxy_alternate_job; + if (set_alternative_proxy_server) { + // Mock data for QUIC proxy socket. + socket_data_proxy_alternate_job.set_connect_data( + MockConnect(ASYNC, mock_error)); + session_deps_.socket_factory->AddSocketDataProvider( + &socket_data_proxy_alternate_job); + test_proxy_delegate->set_alternative_proxy_server( + ProxyServer::FromPacString("QUIC badproxy:99")); + } + + SSLSocketDataProvider ssl_data(ASYNC, OK); + ProxyClientSocketDataProvider proxy_data(ASYNC, mock_error); + + StaticSocketDataProvider socket_data_proxy_main_job; + socket_data_proxy_main_job.set_connect_data(MockConnect(ASYNC, OK)); + session_deps_.socket_factory->AddSocketDataProvider( + &socket_data_proxy_main_job); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); + session_deps_.socket_factory->AddProxyClientSocketDataProvider(&proxy_data); + + // When retrying the job using the second proxy (badfallback:98), + // alternative job must not be created. So, socket data for only the + // main job is needed. + StaticSocketDataProvider socket_data_proxy_main_job_2; + socket_data_proxy_main_job_2.set_connect_data(MockConnect(ASYNC, OK)); + session_deps_.socket_factory->AddSocketDataProvider( + &socket_data_proxy_main_job_2); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); + session_deps_.socket_factory->AddProxyClientSocketDataProvider(&proxy_data); + + // First request would use DIRECT, and succeed. + StaticSocketDataProvider socket_data_direct_first_request; + socket_data_direct_first_request.set_connect_data(MockConnect(ASYNC, OK)); + session_deps_.socket_factory->AddSocketDataProvider( + &socket_data_direct_first_request); + + // Second request would use DIRECT, and succeed. + StaticSocketDataProvider socket_data_direct_second_request; + socket_data_direct_second_request.set_connect_data(MockConnect(ASYNC, OK)); + session_deps_.socket_factory->AddSocketDataProvider( + &socket_data_direct_second_request); + + // Now request a stream. It should succeed using the DIRECT. + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("http://www.example.com"); + + Initialize(std::move(proxy_resolution_service), + std::move(test_proxy_delegate)); + EXPECT_EQ(set_alternative_proxy_server, + test_proxy_delegate_raw->alternative_proxy_server().is_quic()); + + // Start two requests. The first request should consume data from + // |socket_data_proxy_main_job|, + // |socket_data_proxy_alternate_job| and + // |socket_data_direct_first_request|. The second request should consume + // data from |socket_data_direct_second_request|. + + for (size_t i = 0; i < 2; ++i) { + ProxyInfo used_proxy_info; + EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)) + .Times(1) + .WillOnce(::testing::SaveArg<1>(&used_proxy_info)); + + std::unique_ptr<HttpStreamRequest> request = + CreateJobController(request_info); + base::RunLoop().RunUntilIdle(); + + // Verify that request was fetched without proxy. + EXPECT_TRUE(used_proxy_info.is_direct()); + + // The proxies that failed should now be known to the proxy service as + // bad. + const ProxyRetryInfoMap& retry_info = + session_->proxy_resolution_service()->proxy_retry_info(); + EXPECT_THAT(retry_info, SizeIs(set_alternative_proxy_server ? 3 : 2)); + EXPECT_THAT(retry_info, Contains(Key("https://badproxy:99"))); + EXPECT_THAT(retry_info, Contains(Key("https://badfallbackproxy:98"))); + + if (set_alternative_proxy_server) + EXPECT_THAT(retry_info, Contains(Key("quic://badproxy:99"))); + } + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +// Tests that ERR_MSG_TOO_BIG is retryable for QUIC proxy. +TEST_F(JobControllerReconsiderProxyAfterErrorTest, ReconsiderErrMsgTooBig) { + session_deps_.socket_factory->UseMockProxyClientSockets(); + std::unique_ptr<ProxyResolutionService> proxy_resolution_service = + ProxyResolutionService::CreateFixedFromPacResult( + "QUIC badproxy:99; DIRECT", TRAFFIC_ANNOTATION_FOR_TESTS); + + // Before starting the test, verify that there are no proxies marked as bad. + ASSERT_TRUE(proxy_resolution_service->proxy_retry_info().empty()); + + // Mock data for the QUIC proxy socket. + StaticSocketDataProvider quic_proxy_socket; + quic_proxy_socket.set_connect_data(MockConnect(ASYNC, ERR_MSG_TOO_BIG)); + session_deps_.socket_factory->AddSocketDataProvider(&quic_proxy_socket); + + // Mock data for DIRECT. + StaticSocketDataProvider socket_data_direct; + socket_data_direct.set_connect_data(MockConnect(ASYNC, OK)); + session_deps_.socket_factory->AddSocketDataProvider(&socket_data_direct); + + // Now request a stream. It should fallback to DIRECT on ERR_MSG_TOO_BIG. + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("http://www.example.com"); + + Initialize(std::move(proxy_resolution_service), + std::make_unique<TestProxyDelegate>()); + + ProxyInfo used_proxy_info; + EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)) + .Times(1) + .WillOnce(::testing::SaveArg<1>(&used_proxy_info)); + + std::unique_ptr<HttpStreamRequest> request = + CreateJobController(request_info); + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(used_proxy_info.is_direct()); + const ProxyRetryInfoMap& retry_info = + session_->proxy_resolution_service()->proxy_retry_info(); + EXPECT_THAT(retry_info, SizeIs(1)); + EXPECT_THAT(retry_info, Contains(Key("quic://badproxy:99"))); + + request.reset(); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +// Same as test above except that this is testing the retry behavior for +// non-QUIC proxy on ERR_MSG_TOO_BIG. +TEST_F(JobControllerReconsiderProxyAfterErrorTest, + DoNotReconsiderErrMsgTooBig) { + session_deps_.socket_factory->UseMockProxyClientSockets(); + std::unique_ptr<ProxyResolutionService> proxy_resolution_service = + ProxyResolutionService::CreateFixedFromPacResult( + "HTTPS badproxy:99; DIRECT", TRAFFIC_ANNOTATION_FOR_TESTS); + + // Before starting the test, verify that there are no proxies marked as bad. + ASSERT_TRUE(proxy_resolution_service->proxy_retry_info().empty()); + + // Mock data for the HTTPS proxy socket. + SSLSocketDataProvider ssl_data(ASYNC, OK); + ProxyClientSocketDataProvider proxy_data(ASYNC, ERR_MSG_TOO_BIG); + StaticSocketDataProvider https_proxy_socket; + https_proxy_socket.set_connect_data(MockConnect(ASYNC, OK)); + session_deps_.socket_factory->AddSocketDataProvider(&https_proxy_socket); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); + session_deps_.socket_factory->AddProxyClientSocketDataProvider(&proxy_data); + + // Now request a stream. It should not fallback to DIRECT on ERR_MSG_TOO_BIG. + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("http://www.example.com"); + + Initialize(std::move(proxy_resolution_service), + std::make_unique<TestProxyDelegate>()); + + ProxyInfo used_proxy_info; + EXPECT_CALL(request_delegate_, OnStreamFailed(ERR_MSG_TOO_BIG, _, _)) + .Times(1); + + std::unique_ptr<HttpStreamRequest> request = + CreateJobController(request_info); + base::RunLoop().RunUntilIdle(); + + const ProxyRetryInfoMap& retry_info = + session_->proxy_resolution_service()->proxy_retry_info(); + EXPECT_THAT(retry_info, SizeIs(0)); + + request.reset(); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +// Tests that the main (HTTP) job is started after the alternative +// proxy server job has failed. There are 3 jobs in total that are run +// in the following sequence: alternative proxy server job, +// delayed HTTP job with the first proxy server, HTTP job with +// the second proxy configuration. The result of the last job (OK) +// should be returned to the delegate. +TEST_F(JobControllerReconsiderProxyAfterErrorTest, + SecondMainJobIsStartedAfterAltProxyServerJobFailed) { + // Configure the proxies and initialize the test. + std::unique_ptr<ProxyResolutionService> proxy_resolution_service = + ProxyResolutionService::CreateFixedFromPacResult( + "HTTPS myproxy.org:443; DIRECT", TRAFFIC_ANNOTATION_FOR_TESTS); + + auto test_proxy_delegate = std::make_unique<TestProxyDelegate>(); + test_proxy_delegate->set_alternative_proxy_server( + ProxyServer::FromPacString("QUIC myproxy.org:443")); + + Initialize(std::move(proxy_resolution_service), + std::move(test_proxy_delegate)); + + // Enable delayed TCP and set time delay for waiting job. + QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); + quic_stream_factory->set_require_confirmation(false); + ServerNetworkStats stats1; + stats1.srtt = base::TimeDelta::FromSeconds(100); + session_->http_server_properties()->SetServerNetworkStats( + url::SchemeHostPort(GURL("http://www.example.com")), stats1); + + // Prepare the mocked data. + MockQuicData quic_data; + quic_data.AddRead(ASYNC, ERR_QUIC_PROTOCOL_ERROR); + quic_data.AddWrite(ASYNC, OK); + quic_data.AddSocketDataToFactory(session_deps_.socket_factory.get()); + + StaticSocketDataProvider tcp_data_1; + tcp_data_1.set_connect_data(MockConnect(SYNCHRONOUS, ERR_CONNECTION_REFUSED)); + session_deps_.socket_factory->AddSocketDataProvider(&tcp_data_1); + + StaticSocketDataProvider tcp_data_2; + tcp_data_2.set_connect_data(MockConnect(SYNCHRONOUS, OK)); + session_deps_.socket_factory->AddSocketDataProvider(&tcp_data_2); + SSLSocketDataProvider ssl_data(SYNCHRONOUS, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); + + // Create a request. + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("http://www.example.com"); + AlternativeService alternative_service(kProtoQUIC, "www.example.com", 80); + SetAlternativeService(request_info, alternative_service); + + EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)).Times(1); + EXPECT_CALL(request_delegate_, OnStreamFailed(_, _, _)).Times(0); + + // Create the job controller. + std::unique_ptr<HttpStreamRequest> request = + CreateJobController(request_info); + + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(quic_data.AllReadDataConsumed()); + EXPECT_TRUE(quic_data.AllWriteDataConsumed()); + EXPECT_TRUE(tcp_data_1.AllReadDataConsumed()); + EXPECT_TRUE(tcp_data_1.AllWriteDataConsumed()); + EXPECT_TRUE(tcp_data_2.AllReadDataConsumed()); + EXPECT_TRUE(tcp_data_2.AllWriteDataConsumed()); +} + +TEST_F(HttpStreamFactoryJobControllerTest, OnStreamFailedWithNoAlternativeJob) { + tcp_data_ = std::make_unique<SequencedSocketData>(); + tcp_data_->set_connect_data(MockConnect(ASYNC, ERR_FAILED)); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("http://www.google.com"); + + Initialize(request_info); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_FALSE(job_controller_->alternative_job()); + + // There's no other alternative job. Thus when stream failed, it should + // notify Request of the stream failure. + EXPECT_CALL(request_delegate_, OnStreamFailed(ERR_FAILED, _, _)).Times(1); + base::RunLoop().RunUntilIdle(); +} + +TEST_F(HttpStreamFactoryJobControllerTest, OnStreamReadyWithNoAlternativeJob) { + tcp_data_ = std::make_unique<SequencedSocketData>(); + tcp_data_->set_connect_data(MockConnect(ASYNC, OK)); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("http://www.google.com"); + + Initialize(request_info); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + + // There's no other alternative job. Thus when a stream is ready, it should + // notify Request. + EXPECT_TRUE(job_controller_->main_job()); + + EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); + base::RunLoop().RunUntilIdle(); +} + +// Test we cancel Jobs correctly when the Request is explicitly canceled +// before any Job is bound to Request. +TEST_F(HttpStreamFactoryJobControllerTest, CancelJobsBeforeBinding) { + // Use COLD_START to make the alt job pending. + crypto_client_stream_factory_.set_handshake_mode( + MockCryptoClientStream::COLD_START); + quic_data_ = std::make_unique<MockQuicData>(); + quic_data_->AddRead(SYNCHRONOUS, OK); + + tcp_data_ = std::make_unique<SequencedSocketData>(); + tcp_data_->set_connect_data(MockConnect(ASYNC, OK)); + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + + Initialize(request_info); + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + SetAlternativeService(request_info, alternative_service); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + + // Reset the Request will cancel all the Jobs since there's no Job determined + // to serve Request yet and JobController will notify the factory to delete + // itself upon completion. + request_.reset(); + VerifyBrokenAlternateProtocolMapping(request_info, false); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +// Test that the controller does not create alternative job when the advertised +// versions in AlternativeServiceInfo do not contain any version that is +// supported. +TEST_F(HttpStreamFactoryJobControllerTest, + DoNotCreateAltJobIfQuicVersionsUnsupported) { + tcp_data_ = std::make_unique<SequencedSocketData>(); + tcp_data_->set_connect_data(MockConnect(ASYNC, OK)); + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + + Initialize(request_info); + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1); + session_->http_server_properties()->SetQuicAlternativeService( + server, alternative_service, expiration, {QUIC_VERSION_UNSUPPORTED}); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_FALSE(job_controller_->alternative_job()); + + request_.reset(); + VerifyBrokenAlternateProtocolMapping(request_info, false); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +TEST_F(HttpStreamFactoryJobControllerTest, OnStreamFailedForBothJobs) { + quic_data_ = std::make_unique<MockQuicData>(); + quic_data_->AddConnect(ASYNC, ERR_FAILED); + tcp_data_ = std::make_unique<SequencedSocketData>(); + tcp_data_->set_connect_data(MockConnect(ASYNC, ERR_FAILED)); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + + Initialize(request_info); + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + SetAlternativeService(request_info, alternative_service); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + + // The failure of second Job should be reported to Request as there's no more + // pending Job to serve the Request. + EXPECT_CALL(request_delegate_, OnStreamFailed(_, _, _)).Times(1); + base::RunLoop().RunUntilIdle(); + VerifyBrokenAlternateProtocolMapping(request_info, false); + request_.reset(); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +TEST_F(HttpStreamFactoryJobControllerTest, AltJobFailsAfterMainJobSucceeds) { + quic_data_ = std::make_unique<MockQuicData>(); + quic_data_->AddRead(ASYNC, ERR_FAILED); + crypto_client_stream_factory_.set_handshake_mode( + MockCryptoClientStream::COLD_START); + + tcp_data_ = std::make_unique<SequencedSocketData>(); + tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); + SSLSocketDataProvider ssl_data(SYNCHRONOUS, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + + Initialize(request_info); + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + SetAlternativeService(request_info, alternative_service); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + + // Main job succeeds, starts serving Request and it should report status + // to Request. The alternative job will mark the main job complete and gets + // orphaned. + EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); + // JobController shouldn't report the status of second job as request + // is already successfully served. + EXPECT_CALL(request_delegate_, OnStreamFailed(_, _, _)).Times(0); + + base::RunLoop().RunUntilIdle(); + + VerifyBrokenAlternateProtocolMapping(request_info, true); + // Reset the request as it's been successfully served. + request_.reset(); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +// Tests that when alt job succeeds, main job is destroyed. +TEST_F(HttpStreamFactoryJobControllerTest, AltJobSucceedsMainJobDestroyed) { + quic_data_ = std::make_unique<MockQuicData>(); + quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING); + // Use cold start and complete alt job manually. + crypto_client_stream_factory_.set_handshake_mode( + MockCryptoClientStream::COLD_START); + tcp_data_ = std::make_unique<SequencedSocketData>(); + tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING)); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + + Initialize(request_info); + + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + SetAlternativeService(request_info, alternative_service); + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_)); + + // Make |alternative_job| succeed. + auto http_stream = std::make_unique<HttpBasicStream>( + std::make_unique<ClientSocketHandle>(), false, false); + EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, http_stream.get())); + + HttpStreamFactoryJobPeer::SetStream(job_factory_.alternative_job(), + std::move(http_stream)); + job_controller_->OnStreamReady(job_factory_.alternative_job(), SSLConfig()); + + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + + request_.reset(); + VerifyBrokenAlternateProtocolMapping(request_info, false); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +// Tests that if alt job succeeds and main job is blocked, main job should be +// cancelled immediately. |request_| completion will clean up the JobController. +// Regression test for crbug.com/678768. +TEST_F(HttpStreamFactoryJobControllerTest, + AltJobSucceedsMainJobBlockedControllerDestroyed) { + quic_data_ = std::make_unique<MockQuicData>(); + quic_data_->AddWrite(SYNCHRONOUS, + client_maker_.MakeInitialSettingsPacket(1, nullptr)); + quic_data_->AddRead(ASYNC, OK); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + + Initialize(request_info); + + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + SetAlternativeService(request_info, alternative_service); + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + EXPECT_TRUE(JobControllerPeer::main_job_is_blocked(job_controller_)); + + // |alternative_job| succeeds and should report status to |request_delegate_|. + EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); + + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + + // Invoke OnRequestComplete() which should delete |job_controller_| from + // |factory_|. + request_.reset(); + VerifyBrokenAlternateProtocolMapping(request_info, false); + // This fails without the fix for crbug.com/678768. + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +TEST_F(HttpStreamFactoryJobControllerTest, + SpdySessionKeyHasOriginHostPortPair) { + session_deps_.enable_http2_alternative_service = true; + + const char origin_host[] = "www.example.org"; + const uint16_t origin_port = 443; + const char alternative_host[] = "mail.example.org"; + const uint16_t alternative_port = 123; + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = + GURL(base::StringPrintf("https://%s:%u", origin_host, origin_port)); + Initialize(request_info); + + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoHTTP2, alternative_host, + alternative_port); + SetAlternativeService(request_info, alternative_service); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + + HostPortPair main_host_port_pair = + HttpStreamFactoryJobPeer::GetSpdySessionKey(job_controller_->main_job()) + .host_port_pair(); + EXPECT_EQ(origin_host, main_host_port_pair.host()); + EXPECT_EQ(origin_port, main_host_port_pair.port()); + + HostPortPair alternative_host_port_pair = + HttpStreamFactoryJobPeer::GetSpdySessionKey( + job_controller_->alternative_job()) + .host_port_pair(); + EXPECT_EQ(origin_host, alternative_host_port_pair.host()); + EXPECT_EQ(origin_port, alternative_host_port_pair.port()); +} + +// Tests that if an orphaned job completes after |request_| is gone, +// JobController will be cleaned up. +TEST_F(HttpStreamFactoryJobControllerTest, + OrphanedJobCompletesControllerDestroyed) { + quic_data_ = std::make_unique<MockQuicData>(); + quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING); + // Use cold start and complete alt job manually. + crypto_client_stream_factory_.set_handshake_mode( + MockCryptoClientStream::COLD_START); + + tcp_data_ = std::make_unique<SequencedSocketData>(); + tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); + SSLSocketDataProvider ssl_data(ASYNC, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + + Initialize(request_info); + + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + SetAlternativeService(request_info, alternative_service); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + // main job should not be blocked because alt job returned ERR_IO_PENDING. + EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_)); + + EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); + + // Complete main job now. + base::RunLoop().RunUntilIdle(); + + // Invoke OnRequestComplete() which should not delete |job_controller_| from + // |factory_| because alt job is yet to finish. + request_.reset(); + ASSERT_FALSE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); + EXPECT_FALSE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + + // Make |alternative_job| succeed. + auto http_stream = std::make_unique<HttpBasicStream>( + std::make_unique<ClientSocketHandle>(), false, false); + HttpStreamFactoryJobPeer::SetStream(job_factory_.alternative_job(), + std::move(http_stream)); + // This should not call request_delegate_::OnStreamReady. + job_controller_->OnStreamReady(job_factory_.alternative_job(), SSLConfig()); + // Make sure that controller does not leak. + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +TEST_F(HttpStreamFactoryJobControllerTest, AltJobSucceedsAfterMainJobFailed) { + quic_data_ = std::make_unique<MockQuicData>(); + quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING); + // Use cold start and complete alt job manually. + crypto_client_stream_factory_.set_handshake_mode( + MockCryptoClientStream::COLD_START); + + // One failed TCP connect. + tcp_data_ = std::make_unique<SequencedSocketData>(); + tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, ERR_FAILED)); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + + Initialize(request_info); + + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + SetAlternativeService(request_info, alternative_service); + + // |main_job| fails but should not report status to Request. + EXPECT_CALL(request_delegate_, OnStreamFailed(_, _, _)).Times(0); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + + base::RunLoop().RunUntilIdle(); + + // Make |alternative_job| succeed. + auto http_stream = std::make_unique<HttpBasicStream>( + std::make_unique<ClientSocketHandle>(), false, false); + EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, http_stream.get())); + + HttpStreamFactoryJobPeer::SetStream(job_factory_.alternative_job(), + std::move(http_stream)); + job_controller_->OnStreamReady(job_factory_.alternative_job(), SSLConfig()); + + // |alternative_job| succeeds and should report status to Request. + VerifyBrokenAlternateProtocolMapping(request_info, false); + request_.reset(); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +TEST_F(HttpStreamFactoryJobControllerTest, MainJobSucceedsAfterAltJobFailed) { + quic_data_ = std::make_unique<MockQuicData>(); + quic_data_->AddConnect(SYNCHRONOUS, ERR_FAILED); + + tcp_data_ = std::make_unique<SequencedSocketData>(); + tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); + SSLSocketDataProvider ssl_data(ASYNC, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); + + base::HistogramTester histogram_tester; + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + + Initialize(request_info); + + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + SetAlternativeService(request_info, alternative_service); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + + // |alternative_job| fails but should not report status to Request. + EXPECT_CALL(request_delegate_, OnStreamFailed(_, _, _)).Times(0); + // |main_job| succeeds and should report status to Request. + EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); + + base::RunLoop().RunUntilIdle(); + + // Verify that the alternate protocol is marked as broken. + VerifyBrokenAlternateProtocolMapping(request_info, true); + histogram_tester.ExpectUniqueSample("Net.AlternateServiceFailed", -ERR_FAILED, + 1); + request_.reset(); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +// Verifies that if the alternative job fails due to a connection change event, +// then the alternative service is not marked as broken. +TEST_F(HttpStreamFactoryJobControllerTest, + MainJobSucceedsAfterConnectionChanged) { + quic_data_ = std::make_unique<MockQuicData>(); + quic_data_->AddConnect(SYNCHRONOUS, ERR_NETWORK_CHANGED); + tcp_data_ = std::make_unique<SequencedSocketData>(); + tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); + SSLSocketDataProvider ssl_data(ASYNC, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); + + base::HistogramTester histogram_tester; + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + Initialize(request_info); + + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + SetAlternativeService(request_info, alternative_service); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + + // |alternative_job| fails but should not report status to Request. + EXPECT_CALL(request_delegate_, OnStreamFailed(_, _, _)).Times(0); + // |main_job| succeeds and should report status to Request. + EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); + base::RunLoop().RunUntilIdle(); + + // Verify that the alternate protocol is not marked as broken. + VerifyBrokenAlternateProtocolMapping(request_info, false); + histogram_tester.ExpectUniqueSample("Net.AlternateServiceFailed", + -ERR_NETWORK_CHANGED, 1); + request_.reset(); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +// Regression test for crbug/621069. +// Get load state after main job fails and before alternative job succeeds. +TEST_F(HttpStreamFactoryJobControllerTest, GetLoadStateAfterMainJobFailed) { + // Use COLD_START to complete alt job manually. + quic_data_ = std::make_unique<MockQuicData>(); + quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING); + crypto_client_stream_factory_.set_handshake_mode( + MockCryptoClientStream::COLD_START); + + tcp_data_ = std::make_unique<SequencedSocketData>(); + tcp_data_->set_connect_data(MockConnect(ASYNC, ERR_FAILED)); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + + Initialize(request_info); + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + SetAlternativeService(request_info, alternative_service); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + + // |main_job| fails but should not report status to Request. + // The alternative job will mark the main job complete. + EXPECT_CALL(request_delegate_, OnStreamFailed(_, _, _)).Times(0); + + base::RunLoop().RunUntilIdle(); + + // Controller should use alternative job to get load state. + job_controller_->GetLoadState(); + + // |alternative_job| succeeds and should report status to Request. + auto http_stream = std::make_unique<HttpBasicStream>( + std::make_unique<ClientSocketHandle>(), false, false); + EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, http_stream.get())); + + HttpStreamFactoryJobPeer::SetStream(job_factory_.alternative_job(), + std::move(http_stream)); + job_controller_->OnStreamReady(job_factory_.alternative_job(), SSLConfig()); + + request_.reset(); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +TEST_F(HttpStreamFactoryJobControllerTest, ResumeMainJobWhenAltJobStalls) { + // Use COLD_START to stall alt job. + quic_data_ = std::make_unique<MockQuicData>(); + quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING); + crypto_client_stream_factory_.set_handshake_mode( + MockCryptoClientStream::COLD_START); + + tcp_data_ = std::make_unique<SequencedSocketData>(); + tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); + SSLSocketDataProvider ssl_data(ASYNC, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + + Initialize(request_info); + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + SetAlternativeService(request_info, alternative_service); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + + // Alt job is stalled and main job should complete successfully. + EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); + + base::RunLoop().RunUntilIdle(); +} + +TEST_F(HttpStreamFactoryJobControllerTest, InvalidPortForQuic) { + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + + // Using a restricted port 101 for QUIC should fail and the alternative job + // should post OnStreamFailedCall on the controller to resume the main job. + Initialize(request_info); + + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 101); + SetAlternativeService(request_info, alternative_service); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + + EXPECT_TRUE(job_factory_.main_job()->is_waiting()); + + // Wait until OnStreamFailedCallback is executed on the alternative job. + EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1); + base::RunLoop().RunUntilIdle(); +} + +// Verifies that the main job is not resumed until after the alt job completes +// host resolution. +TEST_F(HttpStreamFactoryJobControllerTest, HostResolutionHang) { + auto hanging_resolver = std::make_unique<MockHostResolver>(); + hanging_resolver->set_ondemand_mode(true); + hanging_resolver->set_synchronous_mode(false); + session_deps_.host_resolver = std::move(hanging_resolver); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + + Initialize(request_info); + + // handshake will fail asynchronously after mock data is unpaused. + MockQuicData quic_data; + quic_data.AddRead(ASYNC, ERR_IO_PENDING); // Pause + quic_data.AddRead(ASYNC, ERR_FAILED); + quic_data.AddWrite(ASYNC, ERR_FAILED); + quic_data.AddSocketDataToFactory(session_deps_.socket_factory.get()); + + // Enable delayed TCP and set time delay for waiting job. + QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); + quic_stream_factory->set_require_confirmation(false); + ServerNetworkStats stats1; + stats1.srtt = base::TimeDelta::FromMicroseconds(10); + session_->http_server_properties()->SetServerNetworkStats( + url::SchemeHostPort(GURL("https://www.google.com")), stats1); + + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + SetAlternativeService(request_info, alternative_service); + + // This prevents handshake from immediately succeeding. + crypto_client_stream_factory_.set_handshake_mode( + MockCryptoClientStream::COLD_START); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + EXPECT_TRUE(JobControllerPeer::main_job_is_blocked(job_controller_)); + + // Since the alt job has not finished host resolution, there should be no + // delayed task posted to resume the main job. + EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); + FastForwardBy(base::TimeDelta::FromMicroseconds(50)); + EXPECT_TRUE(JobControllerPeer::main_job_is_blocked(job_controller_)); + + // Allow alt job host resolution to complete. + session_deps_.host_resolver->ResolveAllPending(); + + // Task to resume main job in 15 microseconds should be posted. + EXPECT_TRUE(MainThreadHasPendingTask()); + EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); + FastForwardBy(base::TimeDelta::FromMicroseconds(14)); + EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1); + FastForwardBy(base::TimeDelta::FromMicroseconds(1)); + + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_)); + EXPECT_TRUE(JobControllerPeer::main_job_is_resumed(job_controller_)); + + // Unpause mock quic data. + // Will cause |alternative_job| to fail, but its failure should not be + // reported to Request. + EXPECT_CALL(request_delegate_, OnStreamFailed(_, _, _)).Times(0); + // OnStreamFailed will post a task to resume the main job immediately but + // won't call Resume() on the main job since it's been resumed already. + EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); + quic_data.GetSequencedSocketData()->Resume(); + FastForwardUntilNoTasksRemain(); + // Alt job should be cleaned up + EXPECT_FALSE(job_controller_->alternative_job()); +} + +TEST_F(HttpStreamFactoryJobControllerTest, DelayedTCP) { + auto immediate_resolver = std::make_unique<MockHostResolver>(); + immediate_resolver->set_synchronous_mode(true); + session_deps_.host_resolver = std::move(immediate_resolver); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + + Initialize(request_info); + + // Handshake will fail asynchronously after mock data is unpaused. + MockQuicData quic_data; + quic_data.AddRead(ASYNC, ERR_IO_PENDING); // Pause + quic_data.AddRead(ASYNC, ERR_FAILED); + quic_data.AddWrite(ASYNC, ERR_FAILED); + quic_data.AddSocketDataToFactory(session_deps_.socket_factory.get()); + + // Enable delayed TCP and set time delay for waiting job. + QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); + quic_stream_factory->set_require_confirmation(false); + ServerNetworkStats stats1; + stats1.srtt = base::TimeDelta::FromMicroseconds(10); + session_->http_server_properties()->SetServerNetworkStats( + url::SchemeHostPort(GURL("https://www.google.com")), stats1); + + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + SetAlternativeService(request_info, alternative_service); + + // This prevents handshake from immediately succeeding. + crypto_client_stream_factory_.set_handshake_mode( + MockCryptoClientStream::COLD_START); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + EXPECT_TRUE(job_controller_->main_job()->is_waiting()); + // Main job is not blocked but hasn't resumed yet; it should resume in 15us. + EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_)); + EXPECT_FALSE(JobControllerPeer::main_job_is_resumed(job_controller_)); + + // Task to resume main job in 15us should be posted. + EXPECT_TRUE(MainThreadHasPendingTask()); + EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); + FastForwardBy(base::TimeDelta::FromMicroseconds(14)); + EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1); + FastForwardBy(base::TimeDelta::FromMicroseconds(1)); + + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + EXPECT_TRUE(JobControllerPeer::main_job_is_resumed(job_controller_)); + + // Unpause mock quic data and run all remaining tasks. Alt-job should fail + // and be cleaned up. + quic_data.GetSequencedSocketData()->Resume(); + FastForwardUntilNoTasksRemain(); + EXPECT_FALSE(job_controller_->alternative_job()); +} + +// Regression test for crbug.com/789560. +TEST_F(HttpStreamFactoryJobControllerTest, ResumeMainJobLaterCanceled) { + std::unique_ptr<ProxyResolutionService> proxy_resolution_service = + ProxyResolutionService::CreateDirect(); + ProxyResolutionService* proxy_resolution_service_raw = + proxy_resolution_service.get(); + session_deps_.proxy_resolution_service = std::move(proxy_resolution_service); + + // Using hanging resolver will cause the alternative job to hang indefinitely. + session_deps_.host_resolver = std::make_unique<HangingResolver>(); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + + Initialize(request_info); + + // Enable delayed TCP and set time delay for waiting job. + QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); + quic_stream_factory->set_require_confirmation(false); + ServerNetworkStats stats1; + stats1.srtt = base::TimeDelta::FromMicroseconds(10); + session_->http_server_properties()->SetServerNetworkStats( + url::SchemeHostPort(GURL("https://www.google.com")), stats1); + + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + SetAlternativeService(request_info, alternative_service); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + EXPECT_TRUE(job_controller_->main_job()->is_waiting()); + + base::RunLoop run_loop; + // The main job should be resumed without delay when alt job fails. + EXPECT_CALL(*job_factory_.main_job(), Resume()) + .Times(1) + .WillOnce(Invoke([&run_loop]() { run_loop.Quit(); })); + job_controller_->OnStreamFailed(job_factory_.alternative_job(), + ERR_QUIC_PROTOCOL_ERROR, SSLConfig()); + FastForwardBy(base::TimeDelta::FromMicroseconds(0)); + run_loop.Run(); + EXPECT_FALSE(job_controller_->alternative_job()); + + // Calling ForceReloadProxyConfig will cause the proxy configuration to + // change. It will still be the direct connection but the configuration + // version will be bumped. That is enough for the job controller to restart + // the jobs. + proxy_resolution_service_raw->ForceReloadProxyConfig(); + HttpStreamFactoryJobPeer::SetShouldReconsiderProxy(job_factory_.main_job()); + // Now the alt service is marked as broken (e.g. through a different request), + // so only non-alt job is restarted. + session_->http_server_properties()->MarkAlternativeServiceBroken( + alternative_service); + + job_controller_->OnStreamFailed(job_factory_.main_job(), ERR_FAILED, + SSLConfig()); + // Jobs are restarted. + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_FALSE(job_controller_->alternative_job()); + + // There shouldn't be any ResumeMainJobLater() delayed tasks. + // This EXPECT_CALL will fail before crbug.com/789560 fix. + EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); + FastForwardBy(base::TimeDelta::FromMicroseconds(15)); + + EXPECT_TRUE(job_controller_->main_job()); + request_.reset(); +} + +// Test that main job is blocked for kMaxDelayTimeForMainJob(3s) if +// http_server_properties cached an inappropriate large srtt for the server, +// which would potentially delay the main job for a extremely long time in +// delayed tcp case. +TEST_F(HttpStreamFactoryJobControllerTest, DelayedTCPWithLargeSrtt) { + // The max delay time should be in sync with .cc file. + base::TimeDelta kMaxDelayTimeForMainJob = base::TimeDelta::FromSeconds(3); + + auto immediate_resolver = std::make_unique<MockHostResolver>(); + immediate_resolver->set_synchronous_mode(true); + session_deps_.host_resolver = std::move(immediate_resolver); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + + Initialize(request_info); + + // handshake will fail asynchronously after mock data is unpaused. + MockQuicData quic_data; + quic_data.AddRead(ASYNC, ERR_IO_PENDING); // Pause + quic_data.AddRead(ASYNC, ERR_FAILED); + quic_data.AddWrite(ASYNC, ERR_FAILED); + quic_data.AddSocketDataToFactory(session_deps_.socket_factory.get()); + + // Enable delayed TCP and set time delay for waiting job. + QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); + quic_stream_factory->set_require_confirmation(false); + ServerNetworkStats stats1; + stats1.srtt = base::TimeDelta::FromSeconds(100); + session_->http_server_properties()->SetServerNetworkStats( + url::SchemeHostPort(GURL("https://www.google.com")), stats1); + + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + SetAlternativeService(request_info, alternative_service); + + // This prevents handshake from immediately succeeding. + crypto_client_stream_factory_.set_handshake_mode( + MockCryptoClientStream::COLD_START); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + // Main job is not blocked but hasn't resumed yet; it should resume in 3s. + EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_)); + EXPECT_FALSE(JobControllerPeer::main_job_is_resumed(job_controller_)); + + // Task to resume main job in 3 seconds should be posted. + EXPECT_TRUE(MainThreadHasPendingTask()); + EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); + FastForwardBy(kMaxDelayTimeForMainJob - base::TimeDelta::FromMicroseconds(1)); + EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1); + FastForwardBy(base::TimeDelta::FromMicroseconds(1)); + + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + EXPECT_TRUE(JobControllerPeer::main_job_is_resumed(job_controller_)); + + // Unpause mock quic data and run all remaining tasks. Alt-job should fail + // and be cleaned up. + quic_data.GetSequencedSocketData()->Resume(); + FastForwardUntilNoTasksRemain(); + EXPECT_FALSE(job_controller_->alternative_job()); +} + +TEST_F(HttpStreamFactoryJobControllerTest, + ResumeMainJobImmediatelyOnStreamFailed) { + auto immediate_resolver = std::make_unique<MockHostResolver>(); + immediate_resolver->set_synchronous_mode(true); + session_deps_.host_resolver = std::move(immediate_resolver); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + + Initialize(request_info); + + // handshake will fail asynchronously after mock data is unpaused. + MockQuicData quic_data; + quic_data.AddRead(ASYNC, ERR_IO_PENDING); // Pause + quic_data.AddRead(ASYNC, ERR_FAILED); + quic_data.AddWrite(ASYNC, ERR_FAILED); + quic_data.AddSocketDataToFactory(session_deps_.socket_factory.get()); + + // Enable delayed TCP and set time delay for waiting job. + QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); + quic_stream_factory->set_require_confirmation(false); + ServerNetworkStats stats1; + stats1.srtt = base::TimeDelta::FromMicroseconds(10); + session_->http_server_properties()->SetServerNetworkStats( + url::SchemeHostPort(GURL("https://www.google.com")), stats1); + + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + SetAlternativeService(request_info, alternative_service); + + // This prevents handshake from immediately succeeding. + crypto_client_stream_factory_.set_handshake_mode( + MockCryptoClientStream::COLD_START); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + // Main job is not blocked but hasn't resumed yet; it's scheduled to resume + // in 15us. + EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_)); + EXPECT_FALSE(JobControllerPeer::main_job_is_resumed(job_controller_)); + + // Task to resume main job in 15us should be posted. + EXPECT_TRUE(MainThreadHasPendingTask()); + + EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); + FastForwardBy(base::TimeDelta::FromMicroseconds(1)); + + // Now unpause the mock quic data to fail the alt job. This should immediately + // resume the main job. + EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1); + quic_data.GetSequencedSocketData()->Resume(); + FastForwardBy(base::TimeDelta()); + + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_FALSE(job_controller_->alternative_job()); + EXPECT_TRUE(JobControllerPeer::main_job_is_resumed(job_controller_)); + + // Verify there is another task to resume main job with delay but should + // not call Resume() on the main job as main job has been resumed. + EXPECT_TRUE(MainThreadHasPendingTask()); + EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); + FastForwardBy(base::TimeDelta::FromMicroseconds(15)); + + FastForwardUntilNoTasksRemain(); +} + +// Verifies that the alternative proxy server job is not created if the URL +// scheme is HTTPS. +TEST_F(HttpStreamFactoryJobControllerTest, HttpsURL) { + // Using hanging resolver will cause the alternative job to hang indefinitely. + session_deps_.host_resolver = std::make_unique<HangingResolver>(); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://mail.example.org/"); + Initialize(request_info); + EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_quic()); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_FALSE(job_controller_->main_job()->is_waiting()); + EXPECT_FALSE(job_controller_->alternative_job()); + + EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); + base::RunLoop().RunUntilIdle(); +} + +// Verifies that the alternative proxy server job is not created if the main job +// does not fetch the resource through a proxy. +TEST_F(HttpStreamFactoryJobControllerTest, HttpURLWithNoProxy) { + // Using hanging resolver will cause the alternative job to hang indefinitely. + session_deps_.host_resolver = std::make_unique<HangingResolver>(); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("http://mail.example.org/"); + + Initialize(request_info); + EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_quic()); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_FALSE(job_controller_->main_job()->is_waiting()); + EXPECT_FALSE(job_controller_->alternative_job()); + + EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); + base::RunLoop().RunUntilIdle(); +} + +// Verifies that the main job is resumed properly after a delay when the +// alternative proxy server job hangs. +TEST_F(HttpStreamFactoryJobControllerTest, DelayedTCPAlternativeProxy) { + auto immediate_resolver = std::make_unique<MockHostResolver>(); + immediate_resolver->set_synchronous_mode(true); + session_deps_.host_resolver = std::move(immediate_resolver); + + UseAlternativeProxy(); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("http://www.mail.example.org/"); + + Initialize(request_info); + + EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_quic()); + + // Handshake will fail asynchronously after mock data is unpaused. + MockQuicData quic_data; + quic_data.AddRead(ASYNC, ERR_IO_PENDING); // Pause + quic_data.AddRead(ASYNC, ERR_FAILED); + quic_data.AddWrite(ASYNC, ERR_FAILED); + quic_data.AddSocketDataToFactory(session_deps_.socket_factory.get()); + + // Enable delayed TCP and set time delay for waiting job. + QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); + quic_stream_factory->set_require_confirmation(false); + ServerNetworkStats stats1; + stats1.srtt = base::TimeDelta::FromMicroseconds(10); + session_->http_server_properties()->SetServerNetworkStats( + url::SchemeHostPort(GURL("https://myproxy.org")), stats1); + + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + SetAlternativeService(request_info, alternative_service); + + // This prevents handshake from immediately succeeding. + crypto_client_stream_factory_.set_handshake_mode( + MockCryptoClientStream::COLD_START); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + EXPECT_TRUE(job_controller_->main_job()->is_waiting()); + // Main job is not blocked but hasn't resumed yet; it should resume in 15us. + EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_)); + EXPECT_FALSE(JobControllerPeer::main_job_is_resumed(job_controller_)); + + // Task to resume main job in 15us should be posted. + EXPECT_TRUE(MainThreadHasPendingTask()); + EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0); + FastForwardBy(base::TimeDelta::FromMicroseconds(14)); + EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1); + FastForwardBy(base::TimeDelta::FromMicroseconds(1)); + + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + EXPECT_TRUE(JobControllerPeer::main_job_is_resumed(job_controller_)); + + // Unpause mock quic data and run all remaining tasks. Alt-job should fail + // and be cleaned up. + quic_data.GetSequencedSocketData()->Resume(); + FastForwardUntilNoTasksRemain(); + EXPECT_FALSE(job_controller_->alternative_job()); +} + +// Verifies that if the alternative proxy server job fails immediately, the +// main job is not blocked. +TEST_F(HttpStreamFactoryJobControllerTest, FailAlternativeProxy) { + session_deps_.socket_factory->UseMockProxyClientSockets(); + ProxyClientSocketDataProvider proxy_data(SYNCHRONOUS, OK); + session_deps_.socket_factory->AddProxyClientSocketDataProvider(&proxy_data); + + quic_data_ = std::make_unique<MockQuicData>(); + quic_data_->AddConnect(SYNCHRONOUS, ERR_FAILED); + tcp_data_ = std::make_unique<SequencedSocketData>(); + tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); + SSLSocketDataProvider ssl_data(ASYNC, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); + + UseAlternativeProxy(); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("http://mail.example.org/"); + Initialize(request_info); + EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_quic()); + EXPECT_THAT(session_->proxy_resolution_service()->proxy_retry_info(), + IsEmpty()); + + // Enable delayed TCP and set time delay for waiting job. + QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); + quic_stream_factory->set_require_confirmation(false); + ServerNetworkStats stats1; + stats1.srtt = base::TimeDelta::FromMicroseconds(300 * 1000); + session_->http_server_properties()->SetServerNetworkStats( + url::SchemeHostPort(GURL("https://myproxy.org")), stats1); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + + EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); + + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(job_controller_->alternative_job()); + EXPECT_TRUE(job_controller_->main_job()); + + // The alternative proxy server should be marked as bad. + EXPECT_THAT(session_->proxy_resolution_service()->proxy_retry_info(), + ElementsAre(Key("quic://myproxy.org:443"))); + request_.reset(); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +// Verifies that if the alternative proxy server job fails due to network +// disconnection, then the proxy delegate is not notified. +TEST_F(HttpStreamFactoryJobControllerTest, + InternetDisconnectedAlternativeProxy) { + session_deps_.socket_factory->UseMockProxyClientSockets(); + ProxyClientSocketDataProvider proxy_data(SYNCHRONOUS, OK); + session_deps_.socket_factory->AddProxyClientSocketDataProvider(&proxy_data); + + quic_data_ = std::make_unique<MockQuicData>(); + quic_data_->AddConnect(SYNCHRONOUS, ERR_INTERNET_DISCONNECTED); + tcp_data_ = std::make_unique<SequencedSocketData>(); + tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); + SSLSocketDataProvider ssl_data(ASYNC, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); + + UseAlternativeProxy(); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("http://mail.example.org/"); + Initialize(request_info); + EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_quic()); + + // Enable delayed TCP and set time delay for waiting job. + QuicStreamFactory* quic_stream_factory = session_->quic_stream_factory(); + quic_stream_factory->set_require_confirmation(false); + ServerNetworkStats stats1; + stats1.srtt = base::TimeDelta::FromMicroseconds(300 * 1000); + session_->http_server_properties()->SetServerNetworkStats( + url::SchemeHostPort(GURL("https://myproxy.org")), stats1); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + + EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); + + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(job_controller_->alternative_job()); + EXPECT_TRUE(job_controller_->main_job()); + + // The alternative proxy server should not be marked as bad. + EXPECT_TRUE(test_proxy_delegate()->alternative_proxy_server().is_valid()); + request_.reset(); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +TEST_F(HttpStreamFactoryJobControllerTest, + AlternativeProxyServerJobFailsAfterMainJobSucceeds) { + base::HistogramTester histogram_tester; + + session_deps_.socket_factory->UseMockProxyClientSockets(); + ProxyClientSocketDataProvider proxy_data(SYNCHRONOUS, OK); + session_deps_.socket_factory->AddProxyClientSocketDataProvider(&proxy_data); + + // Use COLD_START to make the alt job pending. + crypto_client_stream_factory_.set_handshake_mode( + MockCryptoClientStream::COLD_START); + quic_data_ = std::make_unique<MockQuicData>(); + quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING); + tcp_data_ = std::make_unique<SequencedSocketData>(); + tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); + SSLSocketDataProvider ssl_data(ASYNC, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); + + UseAlternativeProxy(); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("http://www.google.com"); + Initialize(request_info); + + url::SchemeHostPort server(request_info.url); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + + // Main job succeeds, starts serving Request and it should report status + // to Request. The alternative job will mark the main job complete and gets + // orphaned. + EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); + + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_TRUE(job_controller_->alternative_job()); + + // JobController shouldn't report the status of alternative server job as + // request is already successfully served. + EXPECT_CALL(request_delegate_, OnStreamFailed(_, _, _)).Times(0); + job_controller_->OnStreamFailed(job_factory_.alternative_job(), ERR_FAILED, + SSLConfig()); + + // Reset the request as it's been successfully served. + request_.reset(); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); + + histogram_tester.ExpectUniqueSample("Net.QuicAlternativeProxy.Usage", + 2 /* ALTERNATIVE_PROXY_USAGE_LOST_RACE */, + 1); +} + +TEST_F(HttpStreamFactoryJobControllerTest, PreconnectToHostWithValidAltSvc) { + quic_data_ = std::make_unique<MockQuicData>(); + quic_data_->AddWrite(SYNCHRONOUS, + client_maker_.MakeInitialSettingsPacket(1, nullptr)); + quic_data_->AddRead(ASYNC, OK); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.example.com"); + SetPreconnect(); + + Initialize(request_info); + + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + SetAlternativeService(request_info, alternative_service); + + job_controller_->Preconnect(1); + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_EQ(HttpStreamFactory::PRECONNECT, + job_controller_->main_job()->job_type()); + EXPECT_FALSE(job_controller_->alternative_job()); + + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +// When preconnect to a H2 supported server, only 1 connection is opened. +TEST_F(HttpStreamFactoryJobControllerTest, + PreconnectMultipleStreamsToH2Server) { + tcp_data_ = std::make_unique<SequencedSocketData>(); + tcp_data_->set_connect_data(MockConnect(ASYNC, OK)); + SetPreconnect(); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("http://www.example.com"); + Initialize(request_info); + + // Sets server support HTTP/2. + url::SchemeHostPort server(request_info.url); + session_->http_server_properties()->SetSupportsSpdy(server, true); + + job_controller_->Preconnect(/*num_streams=*/5); + // Only one job is started. + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_FALSE(job_controller_->alternative_job()); + EXPECT_EQ(HttpStreamFactory::PRECONNECT, + job_controller_->main_job()->job_type()); + // There is only 1 connect even though multiple streams were requested. + EXPECT_EQ( + 1, HttpStreamFactoryJobPeer::GetNumStreams(job_controller_->main_job())); + + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +class JobControllerLimitMultipleH2Requests + : public HttpStreamFactoryJobControllerTest { + protected: + const int kNumRequests = 5; + void SetUp() override { SkipCreatingJobController(); } +}; + +TEST_F(JobControllerLimitMultipleH2Requests, MultipleRequests) { + // Make sure there is only one socket connect. + MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)}; + tcp_data_ = + std::make_unique<SequencedSocketData>(reads, base::span<MockWrite>()); + tcp_data_->set_connect_data(MockConnect(ASYNC, OK)); + SSLSocketDataProvider ssl_data(ASYNC, OK); + ssl_data.next_proto = kProtoHTTP2; + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.example.com"); + Initialize(request_info); + SpdySessionPoolPeer pool_peer(session_->spdy_session_pool()); + pool_peer.SetEnableSendingInitialData(false); + + // Sets server support HTTP/2. + url::SchemeHostPort server(request_info.url); + session_->http_server_properties()->SetSupportsSpdy(server, true); + + std::vector<std::unique_ptr<MockHttpStreamRequestDelegate>> request_delegates; + std::vector<std::unique_ptr<HttpStreamRequest>> requests; + for (int i = 0; i < kNumRequests; ++i) { + request_delegates.emplace_back( + std::make_unique<MockHttpStreamRequestDelegate>()); + HttpStreamFactory::JobController* job_controller = + new HttpStreamFactory::JobController( + factory_, request_delegates[i].get(), session_.get(), &job_factory_, + request_info, is_preconnect_, false /* is_websocket */, + enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), + SSLConfig()); + HttpStreamFactoryPeer::AddJobController(factory_, job_controller); + auto request = job_controller->Start( + request_delegates[i].get(), nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller->main_job()); + EXPECT_FALSE(job_controller->alternative_job()); + requests.push_back(std::move(request)); + } + + for (int i = 0; i < kNumRequests; ++i) { + EXPECT_CALL(*request_delegates[i].get(), OnStreamReadyImpl(_, _, _)); + } + + base::RunLoop().RunUntilIdle(); + requests.clear(); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); + TestNetLogEntry::List entries; + size_t log_position = 0; + for (int i = 0; i < kNumRequests - 1; ++i) { + net_log_.GetEntries(&entries); + log_position = ExpectLogContainsSomewhereAfter( + entries, log_position, NetLogEventType::HTTP_STREAM_JOB_THROTTLED, + NetLogEventPhase::NONE); + } +} + +TEST_F(JobControllerLimitMultipleH2Requests, MultipleRequestsFirstRequestHang) { + // First socket connect hang. + SequencedSocketData hangdata; + hangdata.set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING)); + session_deps_.socket_factory->AddSocketDataProvider(&hangdata); + MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)}; + std::list<SequencedSocketData> socket_data; + std::list<SSLSocketDataProvider> ssl_socket_data; + // kNumRequests - 1 will resume themselves after a delay. There will be + // kNumRequests - 1 sockets opened. + for (int i = 0; i < kNumRequests - 1; i++) { + // Only the first one needs a MockRead because subsequent sockets are + // not used to establish a SpdySession. + if (i == 0) { + socket_data.emplace_back(reads, base::span<MockWrite>()); + } else { + socket_data.emplace_back(); + } + socket_data.back().set_connect_data(MockConnect(ASYNC, OK)); + session_deps_.socket_factory->AddSocketDataProvider(&socket_data.back()); + ssl_socket_data.emplace_back(ASYNC, OK); + ssl_socket_data.back().next_proto = kProtoHTTP2; + session_deps_.socket_factory->AddSSLSocketDataProvider( + &ssl_socket_data.back()); + } + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.example.com"); + Initialize(request_info); + SpdySessionPoolPeer pool_peer(session_->spdy_session_pool()); + pool_peer.SetEnableSendingInitialData(false); + + // Sets server support HTTP/2. + url::SchemeHostPort server(request_info.url); + session_->http_server_properties()->SetSupportsSpdy(server, true); + + std::vector<std::unique_ptr<MockHttpStreamRequestDelegate>> request_delegates; + std::vector<std::unique_ptr<HttpStreamRequest>> requests; + for (int i = 0; i < kNumRequests; ++i) { + request_delegates.push_back( + std::make_unique<MockHttpStreamRequestDelegate>()); + HttpStreamFactory::JobController* job_controller = + new HttpStreamFactory::JobController( + factory_, request_delegates[i].get(), session_.get(), &job_factory_, + request_info, is_preconnect_, false /* is_websocket */, + enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), + SSLConfig()); + HttpStreamFactoryPeer::AddJobController(factory_, job_controller); + auto request = job_controller->Start( + request_delegates[i].get(), nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller->main_job()); + EXPECT_FALSE(job_controller->alternative_job()); + requests.push_back(std::move(request)); + } + + for (int i = 0; i < kNumRequests; ++i) { + EXPECT_CALL(*request_delegates[i].get(), OnStreamReadyImpl(_, _, _)); + } + + EXPECT_TRUE(MainThreadHasPendingTask()); + FastForwardBy(base::TimeDelta::FromMilliseconds( + HttpStreamFactory::Job::kHTTP2ThrottleMs)); + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); + requests.clear(); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); + + EXPECT_TRUE(hangdata.AllReadDataConsumed()); + for (const auto& data : socket_data) { + EXPECT_TRUE(data.AllReadDataConsumed()); + EXPECT_TRUE(data.AllWriteDataConsumed()); + } +} + +TEST_F(JobControllerLimitMultipleH2Requests, + MultipleRequestsFirstRequestCanceled) { + MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)}; + SequencedSocketData first_socket(reads, base::span<MockWrite>()); + first_socket.set_connect_data(MockConnect(ASYNC, OK)); + SSLSocketDataProvider first_ssl_data(ASYNC, OK); + first_ssl_data.next_proto = kProtoHTTP2; + session_deps_.socket_factory->AddSocketDataProvider(&first_socket); + session_deps_.socket_factory->AddSSLSocketDataProvider(&first_ssl_data); + std::list<SequencedSocketData> socket_data; + std::list<SSLSocketDataProvider> ssl_socket_data; + // kNumRequests - 1 will be resumed when the first request is canceled. + for (int i = 0; i < kNumRequests - 1; i++) { + socket_data.emplace_back(); + socket_data.back().set_connect_data(MockConnect(ASYNC, OK)); + session_deps_.socket_factory->AddSocketDataProvider(&socket_data.back()); + ssl_socket_data.emplace_back(ASYNC, OK); + ssl_socket_data.back().next_proto = kProtoHTTP2; + session_deps_.socket_factory->AddSSLSocketDataProvider( + &ssl_socket_data.back()); + } + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.example.com"); + Initialize(request_info); + SpdySessionPoolPeer pool_peer(session_->spdy_session_pool()); + pool_peer.SetEnableSendingInitialData(false); + + // Sets server support HTTP/2. + url::SchemeHostPort server(request_info.url); + session_->http_server_properties()->SetSupportsSpdy(server, true); + + std::vector<std::unique_ptr<MockHttpStreamRequestDelegate>> request_delegates; + std::vector<std::unique_ptr<HttpStreamRequest>> requests; + for (int i = 0; i < kNumRequests; ++i) { + request_delegates.emplace_back( + std::make_unique<MockHttpStreamRequestDelegate>()); + HttpStreamFactory::JobController* job_controller = + new HttpStreamFactory::JobController( + factory_, request_delegates[i].get(), session_.get(), &job_factory_, + request_info, is_preconnect_, false /* is_websocket */, + enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), + SSLConfig()); + HttpStreamFactoryPeer::AddJobController(factory_, job_controller); + auto request = job_controller->Start( + request_delegates[i].get(), nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller->main_job()); + EXPECT_FALSE(job_controller->alternative_job()); + requests.push_back(std::move(request)); + } + // Cancel the first one. + requests[0].reset(); + + for (int i = 1; i < kNumRequests; ++i) { + EXPECT_CALL(*request_delegates[i].get(), OnStreamReadyImpl(_, _, _)); + } + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); + requests.clear(); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); + + EXPECT_TRUE(first_socket.AllReadDataConsumed()); + for (const auto& data : socket_data) { + EXPECT_TRUE(data.AllReadDataConsumed()); + EXPECT_TRUE(data.AllWriteDataConsumed()); + } +} + +TEST_F(JobControllerLimitMultipleH2Requests, MultiplePreconnects) { + // Make sure there is only one socket connect. + tcp_data_ = std::make_unique<SequencedSocketData>(); + tcp_data_->set_connect_data(MockConnect(ASYNC, OK)); + SSLSocketDataProvider ssl_data(ASYNC, OK); + ssl_data.next_proto = kProtoHTTP2; + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.example.com"); + SetPreconnect(); + Initialize(request_info); + + // Sets server support HTTP/2. + url::SchemeHostPort server(request_info.url); + session_->http_server_properties()->SetSupportsSpdy(server, true); + + std::vector<std::unique_ptr<MockHttpStreamRequestDelegate>> request_delegates; + for (int i = 0; i < kNumRequests; ++i) { + request_delegates.emplace_back( + std::make_unique<MockHttpStreamRequestDelegate>()); + HttpStreamFactory::JobController* job_controller = + new HttpStreamFactory::JobController( + factory_, request_delegates[i].get(), session_.get(), &job_factory_, + request_info, is_preconnect_, false /* is_websocket */, + enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), + SSLConfig()); + HttpStreamFactoryPeer::AddJobController(factory_, job_controller); + job_controller->Preconnect(1); + EXPECT_TRUE(job_controller->main_job()); + EXPECT_FALSE(job_controller->alternative_job()); + } + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +TEST_F(JobControllerLimitMultipleH2Requests, H1NegotiatedForFirstRequest) { + // First socket is an HTTP/1.1 socket. + SequencedSocketData first_socket; + first_socket.set_connect_data(MockConnect(ASYNC, OK)); + SSLSocketDataProvider ssl_data(ASYNC, OK); + session_deps_.socket_factory->AddSocketDataProvider(&first_socket); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); + // Second socket is an HTTP/2 socket. + MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)}; + SequencedSocketData second_socket(reads, base::span<MockWrite>()); + second_socket.set_connect_data(MockConnect(ASYNC, OK)); + session_deps_.socket_factory->AddSocketDataProvider(&second_socket); + SSLSocketDataProvider second_ssl_data(ASYNC, OK); + second_ssl_data.next_proto = kProtoHTTP2; + session_deps_.socket_factory->AddSSLSocketDataProvider(&second_ssl_data); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.example.com"); + Initialize(request_info); + SpdySessionPoolPeer pool_peer(session_->spdy_session_pool()); + pool_peer.SetEnableSendingInitialData(false); + + // Sets server support HTTP/2. + url::SchemeHostPort server(request_info.url); + session_->http_server_properties()->SetSupportsSpdy(server, true); + + std::vector<std::unique_ptr<MockHttpStreamRequestDelegate>> request_delegates; + std::vector<std::unique_ptr<HttpStreamRequest>> requests; + for (int i = 0; i < 2; ++i) { + request_delegates.emplace_back( + std::make_unique<MockHttpStreamRequestDelegate>()); + HttpStreamFactory::JobController* job_controller = + new HttpStreamFactory::JobController( + factory_, request_delegates[i].get(), session_.get(), &job_factory_, + request_info, is_preconnect_, false /* is_websocket */, + enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), + SSLConfig()); + HttpStreamFactoryPeer::AddJobController(factory_, job_controller); + auto request = job_controller->Start( + request_delegates[i].get(), nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller->main_job()); + EXPECT_FALSE(job_controller->alternative_job()); + requests.push_back(std::move(request)); + } + + for (int i = 0; i < 2; ++i) { + EXPECT_CALL(*request_delegates[i].get(), OnStreamReadyImpl(_, _, _)); + } + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); + requests.clear(); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); + + EXPECT_TRUE(first_socket.AllReadDataConsumed()); + EXPECT_FALSE(second_socket.AllReadDataConsumed()); +} + +// Tests that HTTP/2 throttling logic only applies to non-QUIC jobs. +TEST_F(JobControllerLimitMultipleH2Requests, QuicJobNotThrottled) { + crypto_client_stream_factory_.set_handshake_mode( + MockCryptoClientStream::COLD_START); + quic_data_ = std::make_unique<MockQuicData>(); + quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING); + MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)}; + tcp_data_ = + std::make_unique<SequencedSocketData>(reads, base::span<MockWrite>()); + + tcp_data_->set_connect_data(MockConnect(ASYNC, OK)); + SSLSocketDataProvider ssl_data(ASYNC, OK); + ssl_data.next_proto = kProtoHTTP2; + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + + Initialize(request_info); + SpdySessionPoolPeer pool_peer(session_->spdy_session_pool()); + pool_peer.SetEnableSendingInitialData(false); + + url::SchemeHostPort server(request_info.url); + // Sets server supports QUIC. + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + SetAlternativeService(request_info, alternative_service); + + // Sets server support HTTP/2. + session_->http_server_properties()->SetSupportsSpdy(server, true); + + // Use default job factory so that Resume() is not mocked out. + HttpStreamFactory::JobFactory default_job_factory; + HttpStreamFactory::JobController* job_controller = + new HttpStreamFactory::JobController( + factory_, &request_delegate_, session_.get(), &default_job_factory, + request_info, is_preconnect_, false /* is_websocket */, + enable_ip_based_pooling_, enable_alternative_services_, SSLConfig(), + SSLConfig()); + HttpStreamFactoryPeer::AddJobController(factory_, job_controller); + request_ = + job_controller->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + + EXPECT_TRUE(job_controller->main_job()); + EXPECT_TRUE(job_controller->alternative_job()); + EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); + base::RunLoop().RunUntilIdle(); + TestNetLogEntry::List entries; + net_log_.GetEntries(&entries); + for (auto entry : entries) { + ASSERT_NE(NetLogEventType::HTTP_STREAM_JOB_THROTTLED, entry.type); + } +} + +class HttpStreamFactoryJobControllerMisdirectedRequestRetry + : public HttpStreamFactoryJobControllerTest, + public ::testing::WithParamInterface<::testing::tuple<bool, bool>> {}; + +INSTANTIATE_TEST_CASE_P( + /* no prefix */, + HttpStreamFactoryJobControllerMisdirectedRequestRetry, + ::testing::Combine(::testing::Bool(), ::testing::Bool())); + +TEST_P(HttpStreamFactoryJobControllerMisdirectedRequestRetry, + DisableIPBasedPoolingAndAlternativeServices) { + const bool enable_ip_based_pooling = ::testing::get<0>(GetParam()); + const bool enable_alternative_services = ::testing::get<1>(GetParam()); + if (enable_alternative_services) { + quic_data_ = std::make_unique<MockQuicData>(); + quic_data_->AddConnect(SYNCHRONOUS, OK); + quic_data_->AddWrite(SYNCHRONOUS, + client_maker_.MakeInitialSettingsPacket(1, nullptr)); + quic_data_->AddRead(ASYNC, OK); + } + tcp_data_ = std::make_unique<SequencedSocketData>(); + tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK)); + SSLSocketDataProvider ssl_data(ASYNC, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); + + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + + if (!enable_ip_based_pooling) + DisableIPBasedPooling(); + if (!enable_alternative_services) + DisableAlternativeServices(); + + Initialize(request_info); + + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + SetAlternativeService(request_info, alternative_service); + + request_ = + job_controller_->Start(&request_delegate_, nullptr, net_log_.bound(), + HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY); + EXPECT_TRUE(job_controller_->main_job()); + if (enable_alternative_services) { + EXPECT_TRUE(job_controller_->alternative_job()); + } else { + EXPECT_FALSE(job_controller_->alternative_job()); + } + + // |main_job| succeeds and should report status to Request. + EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _, _)); + base::RunLoop().RunUntilIdle(); +} + +class HttpStreamFactoryJobControllerPreconnectTest + : public HttpStreamFactoryJobControllerTest, + public ::testing::WithParamInterface<bool> { + protected: + void SetUp() override { + if (!GetParam()) { + scoped_feature_list_.InitFromCommandLine(std::string(), + "LimitEarlyPreconnects"); + } + } + + void Initialize() { + session_deps_.http_server_properties = + std::make_unique<MockHttpServerProperties>(); + session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); + factory_ = session_->http_stream_factory(); + request_info_.method = "GET"; + request_info_.url = GURL("https://www.example.com"); + job_controller_ = new HttpStreamFactory::JobController( + factory_, &request_delegate_, session_.get(), &job_factory_, + request_info_, /* is_preconnect = */ true, + /* is_websocket = */ false, + /* enable_ip_based_pooling = */ true, + /* enable_alternative_services = */ true, SSLConfig(), SSLConfig()); + HttpStreamFactoryPeer::AddJobController(factory_, job_controller_); + } + + protected: + void Preconnect(int num_streams) { + job_controller_->Preconnect(num_streams); + // Only one job is started. + EXPECT_TRUE(job_controller_->main_job()); + EXPECT_FALSE(job_controller_->alternative_job()); + } + + private: + base::test::ScopedFeatureList scoped_feature_list_; + HttpRequestInfo request_info_; +}; + +INSTANTIATE_TEST_CASE_P( + /* no prefix */, + HttpStreamFactoryJobControllerPreconnectTest, + ::testing::Bool()); + +TEST_P(HttpStreamFactoryJobControllerPreconnectTest, LimitEarlyPreconnects) { + std::list<SequencedSocketData> providers; + std::list<SSLSocketDataProvider> ssl_providers; + const int kNumPreconects = 5; + MockRead reads[] = {MockRead(ASYNC, OK)}; + // If experiment is not enabled, there are 5 socket connects. + const size_t actual_num_connects = GetParam() ? 1 : kNumPreconects; + for (size_t i = 0; i < actual_num_connects; ++i) { + providers.emplace_back(reads, base::span<MockWrite>()); + session_deps_.socket_factory->AddSocketDataProvider(&providers.back()); + ssl_providers.emplace_back(ASYNC, OK); + session_deps_.socket_factory->AddSSLSocketDataProvider( + &ssl_providers.back()); + } + Initialize(); + Preconnect(kNumPreconects); + // If experiment is enabled, only 1 stream is requested. + EXPECT_EQ((int)actual_num_connects, HttpStreamFactoryJobPeer::GetNumStreams( + job_controller_->main_job())); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_)); +} + +// Test that GetAlternativeServiceInfoFor will include a list of advertised +// versions, which contains a version that is supported. Returns an empty list +// if advertised versions are missing in HttpServerProperties. +TEST_F(HttpStreamFactoryJobControllerTest, GetAlternativeServiceInfoFor) { + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + + Initialize(request_info); + url::SchemeHostPort server(request_info.url); + AlternativeService alternative_service(kProtoQUIC, server.host(), 443); + base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1); + + // Set alternative service with no advertised version. + session_->http_server_properties()->SetQuicAlternativeService( + server, alternative_service, expiration, QuicTransportVersionVector()); + + AlternativeServiceInfo alt_svc_info = + JobControllerPeer::GetAlternativeServiceInfoFor( + job_controller_, request_info, &request_delegate_, + HttpStreamRequest::HTTP_STREAM); + // Verify that JobController get an empty list of supported QUIC versions. + EXPECT_TRUE(alt_svc_info.advertised_versions().empty()); + + // Set alternative service for the same server with the same list of versions + // that is supported. + QuicTransportVersionVector supported_versions = + session_->params().quic_supported_versions; + ASSERT_TRUE(session_->http_server_properties()->SetQuicAlternativeService( + server, alternative_service, expiration, supported_versions)); + + alt_svc_info = JobControllerPeer::GetAlternativeServiceInfoFor( + job_controller_, request_info, &request_delegate_, + HttpStreamRequest::HTTP_STREAM); + std::sort(supported_versions.begin(), supported_versions.end()); + EXPECT_EQ(supported_versions, alt_svc_info.advertised_versions()); + + QuicTransportVersion unsupported_version_1(QUIC_VERSION_UNSUPPORTED); + QuicTransportVersion unsupported_version_2(QUIC_VERSION_UNSUPPORTED); + for (const QuicTransportVersion& version : AllSupportedTransportVersions()) { + if (std::find(supported_versions.begin(), supported_versions.end(), + version) != supported_versions.end()) + continue; + if (unsupported_version_1 == QUIC_VERSION_UNSUPPORTED) { + unsupported_version_1 = version; + continue; + } + unsupported_version_2 = version; + break; + } + + // Set alternative service for the same server with two QUIC versions: + // - one unsupported version: |unsupported_version_1|, + // - one supported version: session_->params().quic_supported_versions[0]. + QuicTransportVersionVector mixed_quic_versions = { + unsupported_version_1, session_->params().quic_supported_versions[0]}; + ASSERT_TRUE(session_->http_server_properties()->SetQuicAlternativeService( + server, alternative_service, expiration, mixed_quic_versions)); + + alt_svc_info = JobControllerPeer::GetAlternativeServiceInfoFor( + job_controller_, request_info, &request_delegate_, + HttpStreamRequest::HTTP_STREAM); + EXPECT_EQ(2u, alt_svc_info.advertised_versions().size()); + // Verify that JobController returns the list of versions specified in set. + std::sort(mixed_quic_versions.begin(), mixed_quic_versions.end()); + EXPECT_EQ(mixed_quic_versions, alt_svc_info.advertised_versions()); + + // Set alternative service for the same server with two unsupported QUIC + // versions: |unsupported_version_1|, |unsupported_version_2|. + ASSERT_TRUE(session_->http_server_properties()->SetQuicAlternativeService( + server, alternative_service, expiration, + {unsupported_version_1, unsupported_version_2})); + + alt_svc_info = JobControllerPeer::GetAlternativeServiceInfoFor( + job_controller_, request_info, &request_delegate_, + HttpStreamRequest::HTTP_STREAM); + // Verify that JobController returns no valid alternative service. + EXPECT_EQ(kProtoUnknown, alt_svc_info.alternative_service().protocol); + EXPECT_EQ(0u, alt_svc_info.advertised_versions().size()); +} + +// Tests that if HttpNetworkSession has a non-empty QUIC host whitelist, +// then GetAlternativeServiceFor() will not return any QUIC alternative service +// that's not on the whitelist. +TEST_F(HttpStreamFactoryJobControllerTest, QuicHostWhitelist) { + HttpRequestInfo request_info; + request_info.method = "GET"; + request_info.url = GURL("https://www.google.com"); + + Initialize(request_info); + + // Set HttpNetworkSession's QUIC host whitelist to only have www.example.com + HttpNetworkSessionPeer session_peer(session_.get()); + session_peer.params()->quic_host_whitelist.insert("www.example.com"); + session_peer.params()->quic_allow_remote_alt_svc = true; + + // Set alternative service for www.google.com to be www.example.com over QUIC. + url::SchemeHostPort server(request_info.url); + base::Time expiration = base::Time::Now() + base::TimeDelta::FromDays(1); + QuicTransportVersionVector supported_versions = + session_->params().quic_supported_versions; + session_->http_server_properties()->SetQuicAlternativeService( + server, AlternativeService(kProtoQUIC, "www.example.com", 443), + expiration, supported_versions); + + AlternativeServiceInfo alt_svc_info = + JobControllerPeer::GetAlternativeServiceInfoFor( + job_controller_, request_info, &request_delegate_, + HttpStreamRequest::HTTP_STREAM); + + std::sort(supported_versions.begin(), supported_versions.end()); + EXPECT_EQ(kProtoQUIC, alt_svc_info.alternative_service().protocol); + EXPECT_EQ(supported_versions, alt_svc_info.advertised_versions()); + + session_->http_server_properties()->SetQuicAlternativeService( + server, AlternativeService(kProtoQUIC, "www.example.org", 443), + expiration, supported_versions); + + alt_svc_info = JobControllerPeer::GetAlternativeServiceInfoFor( + job_controller_, request_info, &request_delegate_, + HttpStreamRequest::HTTP_STREAM); + + EXPECT_EQ(kProtoUnknown, alt_svc_info.alternative_service().protocol); + EXPECT_EQ(0u, alt_svc_info.advertised_versions().size()); +} + +} // namespace test + +} // namespace net |