// Copyright 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 "base/files/file_path.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "net/base/load_timing_info.h"
#include "net/base/network_delegate.h"
#include "net/cert/mock_cert_verifier.h"
#include "net/dns/mapped_host_resolver.h"
#include "net/dns/mock_host_resolver.h"
#include "net/log/net_log_event_type.h"
#include "net/log/test_net_log.h"
#include "net/log/test_net_log_entry.h"
#include "net/quic/crypto/proof_source_chromium.h"
#include "net/test/cert_test_util.h"
#include "net/test/gtest_util.h"
#include "net/test/test_data_directory.h"
#include "net/test/test_with_scoped_task_environment.h"
#include "net/third_party/quic/core/quic_dispatcher.h"
#include "net/third_party/quic/test_tools/crypto_test_utils.h"
#include "net/third_party/quic/tools/quic_memory_cache_backend.h"
#include "net/third_party/quic/tools/quic_simple_dispatcher.h"
#include "net/tools/quic/quic_simple_server.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace net {

namespace {

// This must match the certificate used (quic-chain.pem and quic-leaf-cert.key).
const char kTestServerHost[] = "test.example.com";
// Used as a simple response from the server.
const char kHelloPath[] = "/hello.txt";
const char kHelloBodyValue[] = "Hello from QUIC Server";
const int kHelloStatus = 200;

class URLRequestQuicTest : public TestWithScopedTaskEnvironment {
 protected:
  URLRequestQuicTest() : context_(new TestURLRequestContext(true)) {
    StartQuicServer();

    std::unique_ptr<HttpNetworkSession::Params> params(
        new HttpNetworkSession::Params);
    CertVerifyResult verify_result;
    verify_result.verified_cert = ImportCertFromFile(
        GetTestCertsDirectory(), "quic-chain.pem");
    cert_verifier_.AddResultForCertAndHost(verify_result.verified_cert.get(),
                                           "test.example.com", verify_result,
                                           OK);
    // To simplify the test, and avoid the race with the HTTP request, we force
    // QUIC for these requests.
    params->origins_to_force_quic_on.insert(HostPortPair(kTestServerHost, 443));
    params->enable_quic = true;
    params->enable_server_push_cancellation = true;
    context_->set_host_resolver(host_resolver_.get());
    context_->set_http_network_session_params(std::move(params));
    context_->set_cert_verifier(&cert_verifier_);
    context_->set_net_log(&net_log_);
  }

  void TearDown() override {
    if (server_) {
      server_->Shutdown();
      // If possible, deliver the conncetion close packet to the client before
      // destruct the TestURLRequestContext.
      base::RunLoop().RunUntilIdle();
    }
  }

  // Sets a NetworkDelegate to use for |context_|. Must be done before Init().
  void SetNetworkDelegate(NetworkDelegate* network_delegate) {
    context_->set_network_delegate(network_delegate);
  }

  // Initializes the TestURLRequestContext |context_|.
  void Init() { context_->Init(); }

  std::unique_ptr<URLRequest> CreateRequest(const GURL& url,
                                            RequestPriority priority,
                                            URLRequest::Delegate* delegate) {
    return context_->CreateRequest(url, priority, delegate,
                                   TRAFFIC_ANNOTATION_FOR_TESTS);
  }

  void ExtractNetLog(NetLogEventType type,
                     TestNetLogEntry::List* entry_list) const {
    TestNetLogEntry::List entries;
    net_log_.GetEntries(&entries);

    for (const auto& entry : entries) {
      if (entry.type == type)
        entry_list->push_back(entry);
    }
  }

  unsigned int GetRstErrorCountReceivedByServer(
      quic::QuicRstStreamErrorCode error_code) const {
    return (static_cast<quic::QuicSimpleDispatcher*>(server_->dispatcher()))
        ->GetRstErrorCount(error_code);
  }

  static const NetLogSource FindPushUrlSource(
      const TestNetLogEntry::List& entries,
      const std::string& push_url) {
    std::string entry_push_url;
    for (const auto& entry : entries) {
      if (entry.phase == NetLogEventPhase::BEGIN &&
          entry.source.type ==
              NetLogSourceType::SERVER_PUSH_LOOKUP_TRANSACTION &&
          entry.GetStringValue("push_url", &entry_push_url) &&
          entry_push_url == push_url) {
        return entry.source;
      }
    }
    return NetLogSource();
  }

  static const TestNetLogEntry* FindEndBySource(
      const TestNetLogEntry::List& entries,
      const NetLogSource& source) {
    for (const auto& entry : entries) {
      if (entry.phase == NetLogEventPhase::END &&
          entry.source.type == source.type && entry.source.id == source.id)
        return &entry;
    }
    return nullptr;
  }

 private:
  void StartQuicServer() {
    // Set up in-memory cache.
    memory_cache_backend_.AddSimpleResponse(kTestServerHost, kHelloPath,
                                            kHelloStatus, kHelloBodyValue);
    memory_cache_backend_.InitializeBackend(ServerPushCacheDirectory());
    quic::QuicConfig config;
    // Set up server certs.
    std::unique_ptr<net::ProofSourceChromium> proof_source(
        new net::ProofSourceChromium());
    base::FilePath directory = GetTestCertsDirectory();
    CHECK(proof_source->Initialize(
        directory.Append(FILE_PATH_LITERAL("quic-chain.pem")),
        directory.Append(FILE_PATH_LITERAL("quic-leaf-cert.key")),
        base::FilePath()));
    server_.reset(new QuicSimpleServer(
        quic::test::crypto_test_utils::ProofSourceForTesting(), config,
        quic::QuicCryptoServerConfig::ConfigOptions(),
        quic::AllSupportedVersions(), &memory_cache_backend_));
    int rv =
        server_->Listen(net::IPEndPoint(net::IPAddress::IPv4AllZeros(), 0));
    EXPECT_GE(rv, 0) << "Quic server fails to start";

    std::unique_ptr<MockHostResolver> resolver(new MockHostResolver());
    resolver->rules()->AddRule("test.example.com", "127.0.0.1");
    host_resolver_.reset(new MappedHostResolver(std::move(resolver)));
    // Use a mapped host resolver so that request for test.example.com
    // reach the server running on localhost.
    std::string map_rule = "MAP test.example.com test.example.com:" +
                           base::IntToString(server_->server_address().port());
    EXPECT_TRUE(host_resolver_->AddRuleFromString(map_rule));
  }

  std::string ServerPushCacheDirectory() {
    base::FilePath path;
    base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
    path = path.AppendASCII("net").AppendASCII("data").AppendASCII(
        "quic_http_response_cache_data_with_push");
    // The file path is known to be an ascii string.
    return path.MaybeAsASCII();
  }

  std::unique_ptr<MappedHostResolver> host_resolver_;
  std::unique_ptr<QuicSimpleServer> server_;
  std::unique_ptr<TestURLRequestContext> context_;
  TestNetLog net_log_;
  quic::QuicMemoryCacheBackend memory_cache_backend_;
  MockCertVerifier cert_verifier_;
};

// A URLRequest::Delegate that checks LoadTimingInfo when response headers are
// received.
class CheckLoadTimingDelegate : public TestDelegate {
 public:
  CheckLoadTimingDelegate(bool session_reused)
      : session_reused_(session_reused) {}
  void OnResponseStarted(URLRequest* request, int error) override {
    TestDelegate::OnResponseStarted(request, error);
    LoadTimingInfo load_timing_info;
    request->GetLoadTimingInfo(&load_timing_info);
    assertLoadTimingValid(load_timing_info, session_reused_);
  }

 private:
  void assertLoadTimingValid(const LoadTimingInfo& load_timing_info,
                             bool session_reused) {
    EXPECT_EQ(session_reused, load_timing_info.socket_reused);

    // If |session_reused| is true, these fields should all be null, non-null
    // otherwise.
    EXPECT_EQ(session_reused,
              load_timing_info.connect_timing.connect_start.is_null());
    EXPECT_EQ(session_reused,
              load_timing_info.connect_timing.connect_end.is_null());
    EXPECT_EQ(session_reused,
              load_timing_info.connect_timing.ssl_start.is_null());
    EXPECT_EQ(session_reused,
              load_timing_info.connect_timing.ssl_end.is_null());
    EXPECT_EQ(load_timing_info.connect_timing.connect_start,
              load_timing_info.connect_timing.ssl_start);
    EXPECT_EQ(load_timing_info.connect_timing.connect_end,
              load_timing_info.connect_timing.ssl_end);
    EXPECT_EQ(session_reused,
              load_timing_info.connect_timing.dns_start.is_null());
    EXPECT_EQ(session_reused,
              load_timing_info.connect_timing.dns_end.is_null());
  }

  bool session_reused_;

  DISALLOW_COPY_AND_ASSIGN(CheckLoadTimingDelegate);
};

// A TestNetworkDelegate that invokes |all_requests_completed_callback| when
// |num_expected_requests| requests are completed.
class WaitForCompletionNetworkDelegate : public net::TestNetworkDelegate {
 public:
  WaitForCompletionNetworkDelegate(
      const base::Closure& all_requests_completed_callback,
      size_t num_expected_requests)
      : all_requests_completed_callback_(all_requests_completed_callback),
        num_expected_requests_(num_expected_requests) {}

  void OnCompleted(URLRequest* request, bool started, int net_error) override {
    net::TestNetworkDelegate::OnCompleted(request, started, net_error);
    num_expected_requests_--;
    if (num_expected_requests_ == 0)
      all_requests_completed_callback_.Run();
  }

 private:
  const base::Closure all_requests_completed_callback_;
  size_t num_expected_requests_;
  DISALLOW_COPY_AND_ASSIGN(WaitForCompletionNetworkDelegate);
};

}  // namespace

TEST_F(URLRequestQuicTest, TestGetRequest) {
  Init();
  CheckLoadTimingDelegate delegate(false);
  std::string url =
      base::StringPrintf("https://%s%s", kTestServerHost, kHelloPath);
  std::unique_ptr<URLRequest> request =
      CreateRequest(GURL(url), DEFAULT_PRIORITY, &delegate);

  request->Start();
  ASSERT_TRUE(request->is_pending());
  delegate.RunUntilComplete();

  EXPECT_TRUE(request->status().is_success());
  EXPECT_EQ(kHelloBodyValue, delegate.data_received());
}

TEST_F(URLRequestQuicTest, CancelPushIfCached_SomeCached) {
  Init();

  // Send a request to the pushed url: /kitten-1.jpg to pull the resource into
  // cache.
  CheckLoadTimingDelegate delegate_0(false);
  std::string url_0 =
      base::StringPrintf("https://%s%s", kTestServerHost, "/kitten-1.jpg");
  std::unique_ptr<URLRequest> request_0 =
      CreateRequest(GURL(url_0), DEFAULT_PRIORITY, &delegate_0);

  request_0->Start();
  ASSERT_TRUE(request_0->is_pending());

  // Spin the message loop until the client receives the response for the first
  // request.
  do {
    base::RunLoop().RunUntilIdle();
  } while (request_0->status().is_io_pending());
  EXPECT_TRUE(request_0->status().is_success());

  // Send a request to /index2.html which pushes /kitten-1.jpg and /favicon.ico.
  // Should cancel push for /kitten-1.jpg.
  CheckLoadTimingDelegate delegate(true);
  std::string url =
      base::StringPrintf("https://%s%s", kTestServerHost, "/index2.html");
  std::unique_ptr<URLRequest> request =
      CreateRequest(GURL(url), DEFAULT_PRIORITY, &delegate);

  request->Start();
  ASSERT_TRUE(request->is_pending());

  // Spin the message loop until the client receives the response for the second
  // request.
  do {
    base::RunLoop().RunUntilIdle();
  } while (request->status().is_io_pending());
  EXPECT_TRUE(request->status().is_success());

  // Extract net logs on client side to verify push lookup transactions.
  net::TestNetLogEntry::List entries;
  ExtractNetLog(NetLogEventType::SERVER_PUSH_LOOKUP_TRANSACTION, &entries);

  ASSERT_EQ(4u, entries.size());

  std::string value;
  int net_error;
  std::string push_url_1 =
      base::StringPrintf("https://%s%s", kTestServerHost, "/kitten-1.jpg");
  std::string push_url_2 =
      base::StringPrintf("https://%s%s", kTestServerHost, "/favicon.ico");

  const NetLogSource source_1 = FindPushUrlSource(entries, push_url_1);
  EXPECT_TRUE(source_1.IsValid());

  // No net error code for this lookup transaction, the push is found.
  const TestNetLogEntry* end_entry_1 = FindEndBySource(entries, source_1);
  EXPECT_FALSE(end_entry_1->params);
  EXPECT_FALSE(end_entry_1->GetIntegerValue("net_error", &net_error));

  const NetLogSource source_2 = FindPushUrlSource(entries, push_url_2);
  EXPECT_TRUE(source_2.IsValid());
  EXPECT_NE(source_1.id, source_2.id);

  // Net error code -400 is found for this lookup transaction, the push is not
  // found in the cache.
  const TestNetLogEntry* end_entry_2 = FindEndBySource(entries, source_2);
  EXPECT_TRUE(end_entry_2->params);
  EXPECT_TRUE(end_entry_2->GetIntegerValue("net_error", &net_error));
  EXPECT_EQ(net_error, -400);

  // Verify the reset error count received on the server side.
  EXPECT_LE(1u, GetRstErrorCountReceivedByServer(quic::QUIC_STREAM_CANCELLED));
}

TEST_F(URLRequestQuicTest, CancelPushIfCached_AllCached) {
  Init();

  // Send a request to the pushed url: /kitten-1.jpg to pull the resource into
  // cache.
  CheckLoadTimingDelegate delegate_0(false);
  std::string url_0 =
      base::StringPrintf("https://%s%s", kTestServerHost, "/kitten-1.jpg");
  std::unique_ptr<URLRequest> request_0 =
      CreateRequest(GURL(url_0), DEFAULT_PRIORITY, &delegate_0);

  request_0->Start();
  ASSERT_TRUE(request_0->is_pending());

  // Spin the message loop until the client receives the response for the first
  // request.
  do {
    base::RunLoop().RunUntilIdle();
  } while (request_0->status().is_io_pending());
  EXPECT_TRUE(request_0->status().is_success());

  // Send a request to the pushed url: /favicon.ico to pull the resource into
  // cache.
  CheckLoadTimingDelegate delegate_1(true);
  std::string url_1 =
      base::StringPrintf("https://%s%s", kTestServerHost, "/favicon.ico");
  std::unique_ptr<URLRequest> request_1 =
      CreateRequest(GURL(url_1), DEFAULT_PRIORITY, &delegate_1);

  request_1->Start();
  ASSERT_TRUE(request_1->is_pending());

  // Spin the message loop until the client receives the response for the second
  // request.
  do {
    base::RunLoop().RunUntilIdle();
  } while (request_1->status().is_io_pending());
  EXPECT_TRUE(request_1->status().is_success());

  // Send a request to /index2.html which pushes /kitten-1.jpg and /favicon.ico.
  // Should cancel push for both pushed resources, since they're already cached.
  CheckLoadTimingDelegate delegate(true);
  std::string url =
      base::StringPrintf("https://%s%s", kTestServerHost, "/index2.html");
  std::unique_ptr<URLRequest> request =
      CreateRequest(GURL(url), DEFAULT_PRIORITY, &delegate);

  request->Start();
  ASSERT_TRUE(request->is_pending());

  // Spin the message loop until the client receives the response for the third
  // request.
  do {
    base::RunLoop().RunUntilIdle();
  } while (request->status().is_io_pending());
  EXPECT_TRUE(request->status().is_success());

  // Extract net logs on client side to verify push lookup transactions.
  net::TestNetLogEntry::List entries;
  ExtractNetLog(NetLogEventType::SERVER_PUSH_LOOKUP_TRANSACTION, &entries);

  EXPECT_EQ(4u, entries.size());

  std::string value;
  int net_error;
  std::string push_url_1 =
      base::StringPrintf("https://%s%s", kTestServerHost, "/kitten-1.jpg");
  std::string push_url_2 =
      base::StringPrintf("https://%s%s", kTestServerHost, "/favicon.ico");

  const NetLogSource source_1 = FindPushUrlSource(entries, push_url_1);
  EXPECT_TRUE(source_1.IsValid());

  // No net error code for this lookup transaction, the push is found.
  const TestNetLogEntry* end_entry_1 = FindEndBySource(entries, source_1);
  EXPECT_FALSE(end_entry_1->params);
  EXPECT_FALSE(end_entry_1->GetIntegerValue("net_error", &net_error));

  const NetLogSource source_2 = FindPushUrlSource(entries, push_url_2);
  EXPECT_TRUE(source_1.IsValid());
  EXPECT_NE(source_1.id, source_2.id);

  // No net error code for this lookup transaction, the push is found.
  const TestNetLogEntry* end_entry_2 = FindEndBySource(entries, source_2);
  EXPECT_FALSE(end_entry_2->params);
  EXPECT_FALSE(end_entry_2->GetIntegerValue("net_error", &net_error));

  // Verify the reset error count received on the server side.
  EXPECT_LE(2u, GetRstErrorCountReceivedByServer(quic::QUIC_STREAM_CANCELLED));
}

TEST_F(URLRequestQuicTest, DoNotCancelPushIfNotFoundInCache) {
  Init();

  // Send a request to /index2.hmtl which pushes /kitten-1.jpg and /favicon.ico
  // and shouldn't cancel any since neither is in cache.
  CheckLoadTimingDelegate delegate(false);
  std::string url =
      base::StringPrintf("https://%s%s", kTestServerHost, "/index2.html");
  std::unique_ptr<URLRequest> request =
      CreateRequest(GURL(url), DEFAULT_PRIORITY, &delegate);

  request->Start();
  ASSERT_TRUE(request->is_pending());

  // Spin the message loop until the client receives response.
  do {
    base::RunLoop().RunUntilIdle();
  } while (request->status().is_io_pending());
  EXPECT_TRUE(request->status().is_success());

  // Extract net logs on client side to verify push lookup transactions.
  net::TestNetLogEntry::List entries;
  ExtractNetLog(NetLogEventType::SERVER_PUSH_LOOKUP_TRANSACTION, &entries);

  EXPECT_EQ(4u, entries.size());

  std::string value;
  int net_error;
  std::string push_url_1 =
      base::StringPrintf("https://%s%s", kTestServerHost, "/kitten-1.jpg");
  std::string push_url_2 =
      base::StringPrintf("https://%s%s", kTestServerHost, "/favicon.ico");

  const NetLogSource source_1 = FindPushUrlSource(entries, push_url_1);
  EXPECT_TRUE(source_1.IsValid());
  const TestNetLogEntry* end_entry_1 = FindEndBySource(entries, source_1);
  EXPECT_TRUE(end_entry_1->params);
  EXPECT_TRUE(end_entry_1->GetIntegerValue("net_error", &net_error));
  EXPECT_EQ(net_error, -400);

  const NetLogSource source_2 = FindPushUrlSource(entries, push_url_2);
  EXPECT_TRUE(source_2.IsValid());
  EXPECT_NE(source_1.id, source_2.id);
  const TestNetLogEntry* end_entry_2 = FindEndBySource(entries, source_2);
  EXPECT_TRUE(end_entry_2->params);
  EXPECT_TRUE(end_entry_2->GetIntegerValue("net_error", &net_error));
  EXPECT_EQ(net_error, -400);

  // Verify the reset error count received on the server side.
  EXPECT_EQ(0u, GetRstErrorCountReceivedByServer(quic::QUIC_STREAM_CANCELLED));
}

// Tests that if two requests use the same QUIC session, the second request
// should not have |LoadTimingInfo::connect_timing|.
TEST_F(URLRequestQuicTest, TestTwoRequests) {
  base::RunLoop run_loop;
  WaitForCompletionNetworkDelegate network_delegate(
      run_loop.QuitClosure(), /*num_expected_requests=*/2);
  SetNetworkDelegate(&network_delegate);
  Init();
  CheckLoadTimingDelegate delegate(false);
  delegate.set_on_complete(base::DoNothing());
  std::string url =
      base::StringPrintf("https://%s%s", kTestServerHost, kHelloPath);
  std::unique_ptr<URLRequest> request =
      CreateRequest(GURL(url), DEFAULT_PRIORITY, &delegate);

  CheckLoadTimingDelegate delegate2(true);
  delegate2.set_on_complete(base::DoNothing());
  std::unique_ptr<URLRequest> request2 =
      CreateRequest(GURL(url), DEFAULT_PRIORITY, &delegate2);
  request->Start();
  request2->Start();
  ASSERT_TRUE(request->is_pending());
  ASSERT_TRUE(request2->is_pending());
  run_loop.Run();

  EXPECT_TRUE(request->status().is_success());
  EXPECT_TRUE(request2->status().is_success());
  EXPECT_EQ(kHelloBodyValue, delegate.data_received());
  EXPECT_EQ(kHelloBodyValue, delegate2.data_received());
}

TEST_F(URLRequestQuicTest, RequestHeadersCallback) {
  Init();
  HttpRawRequestHeaders raw_headers;
  TestDelegate delegate;
  TestURLRequestContext context;
  HttpRequestHeaders extra_headers;
  extra_headers.SetHeader("X-Foo", "bar");

  std::string url =
      base::StringPrintf("https://%s%s", kTestServerHost, kHelloPath);
  std::unique_ptr<URLRequest> request =
      CreateRequest(GURL(url), DEFAULT_PRIORITY, &delegate);

  request->SetExtraRequestHeaders(extra_headers);
  request->SetRequestHeadersCallback(base::Bind(
      &HttpRawRequestHeaders::Assign, base::Unretained(&raw_headers)));
  request->Start();
  ASSERT_TRUE(request->is_pending());
  do {
    base::RunLoop().RunUntilIdle();
  } while (!delegate.response_started_count());
  EXPECT_FALSE(raw_headers.headers().empty());
  std::string value;
  EXPECT_TRUE(raw_headers.FindHeaderForTest("x-foo", &value));
  EXPECT_EQ("bar", value);
  EXPECT_TRUE(raw_headers.FindHeaderForTest("accept-encoding", &value));
  EXPECT_EQ("gzip, deflate", value);
  EXPECT_TRUE(raw_headers.FindHeaderForTest(":path", &value));
  EXPECT_EQ("/hello.txt", value);
  EXPECT_TRUE(raw_headers.FindHeaderForTest(":authority", &value));
  EXPECT_EQ("test.example.com", value);
  EXPECT_TRUE(raw_headers.request_line().empty());
}

}  // namespace net
