// 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 "components/safe_browsing/db/v4_update_protocol_manager.h"

#include <memory>
#include <utility>
#include <vector>

#include "base/base64.h"
#include "base/strings/stringprintf.h"
#include "base/test/test_simple_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "components/safe_browsing/db/safebrowsing.pb.h"
#include "components/safe_browsing/db/util.h"
#include "components/safe_browsing/db/v4_test_util.h"
#include "net/base/escape.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/http/http_status_code.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"

using base::Time;
using base::TimeDelta;

namespace safe_browsing {

class V4UpdateProtocolManagerTest : public PlatformTest {
  void SetUp() override {
    PlatformTest::SetUp();

    SetupStoreStates();
    test_shared_loader_factory_ =
        base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
            &test_url_loader_factory_);
  }

 protected:
  void ValidateGetUpdatesResults(
      const std::vector<ListUpdateResponse>& expected_lurs,
      std::unique_ptr<ParsedServerResponse> parsed_server_response) {
    // The callback should never be called if expect_callback_to_be_called_ is
    // false.
    EXPECT_TRUE(expect_callback_to_be_called_);
    ASSERT_EQ(expected_lurs.size(), parsed_server_response->size());

    for (unsigned int i = 0; i < parsed_server_response->size(); ++i) {
      const ListUpdateResponse& expected = expected_lurs[i];
      const std::unique_ptr<ListUpdateResponse>& actual =
          (*parsed_server_response)[i];
      EXPECT_EQ(expected.platform_type(), actual->platform_type());
      EXPECT_EQ(expected.response_type(), actual->response_type());
      EXPECT_EQ(expected.threat_entry_type(), actual->threat_entry_type());
      EXPECT_EQ(expected.threat_type(), actual->threat_type());
      EXPECT_EQ(expected.new_client_state(), actual->new_client_state());

      // TODO(vakh): Test more fields from the proto.
    }
  }

  ExtendedReportingLevel GetExtendedReportingLevel(ExtendedReportingLevel erl) {
    return erl;
  }

  std::unique_ptr<V4UpdateProtocolManager> CreateProtocolManager(
      const std::vector<ListUpdateResponse>& expected_lurs,
      bool disable_auto_update = false,
      ExtendedReportingLevel erl = SBER_LEVEL_OFF) {
    return V4UpdateProtocolManager::Create(
        test_shared_loader_factory_,
        GetTestV4ProtocolConfig(disable_auto_update),
        base::Bind(&V4UpdateProtocolManagerTest::ValidateGetUpdatesResults,
                   base::Unretained(this), expected_lurs),
        base::Bind(&V4UpdateProtocolManagerTest::GetExtendedReportingLevel,
                   base::Unretained(this), erl));
  }

  void SetupStoreStates() {
    store_state_map_ = std::make_unique<StoreStateMap>();

    ListIdentifier win_url_malware(WINDOWS_PLATFORM, URL, MALWARE_THREAT);
    store_state_map_->insert({win_url_malware, "initial_state_1"});

    ListIdentifier win_url_uws(WINDOWS_PLATFORM, URL, UNWANTED_SOFTWARE);
    store_state_map_->insert({win_url_uws, "initial_state_2"});

    ListIdentifier win_exe_uws(WINDOWS_PLATFORM, EXECUTABLE, UNWANTED_SOFTWARE);
    store_state_map_->insert({win_exe_uws, "initial_state_3"});
  }

  void SetupExpectedListUpdateResponse(
      std::vector<ListUpdateResponse>* expected_lurs) {
    ListUpdateResponse lur;
    lur.set_platform_type(WINDOWS_PLATFORM);
    lur.set_response_type(ListUpdateResponse::PARTIAL_UPDATE);
    lur.set_threat_entry_type(URL);
    lur.set_threat_type(MALWARE_THREAT);
    lur.set_new_client_state("new_state_1");
    expected_lurs->push_back(lur);

    lur.set_platform_type(WINDOWS_PLATFORM);
    lur.set_response_type(ListUpdateResponse::PARTIAL_UPDATE);
    lur.set_threat_entry_type(URL);
    lur.set_threat_type(UNWANTED_SOFTWARE);
    lur.set_new_client_state("new_state_2");
    expected_lurs->push_back(lur);

    lur.set_platform_type(WINDOWS_PLATFORM);
    lur.set_response_type(ListUpdateResponse::FULL_UPDATE);
    lur.set_threat_entry_type(EXECUTABLE);
    lur.set_threat_type(MALWARE_THREAT);
    lur.set_new_client_state("new_state_3");
    expected_lurs->push_back(lur);
  }

  std::string GetExpectedV4UpdateResponse(
      const std::vector<ListUpdateResponse>& expected_lurs) const {
    FetchThreatListUpdatesResponse response;

    for (const auto& expected_lur : expected_lurs) {
      ListUpdateResponse* lur = response.add_list_update_responses();
      lur->set_new_client_state(expected_lur.new_client_state());
      lur->set_platform_type(expected_lur.platform_type());
      lur->set_response_type(expected_lur.response_type());
      lur->set_threat_entry_type(expected_lur.threat_entry_type());
      lur->set_threat_type(expected_lur.threat_type());
    }

    // Serialize.
    std::string res_data;
    response.SerializeToString(&res_data);

    return res_data;
  }

  bool expect_callback_to_be_called_;
  std::unique_ptr<StoreStateMap> store_state_map_;
  network::TestURLLoaderFactory test_url_loader_factory_;
  scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
};

TEST_F(V4UpdateProtocolManagerTest, TestGetUpdatesErrorHandlingNetwork) {
  scoped_refptr<base::TestSimpleTaskRunner> runner(
      new base::TestSimpleTaskRunner());
  base::ThreadTaskRunnerHandle runner_handler(runner);
  const std::vector<ListUpdateResponse> expected_lurs;
  std::unique_ptr<V4UpdateProtocolManager> pm(
      CreateProtocolManager(expected_lurs));
  runner->ClearPendingTasks();

  // Initial state. No errors.
  EXPECT_EQ(0ul, pm->update_error_count_);
  EXPECT_EQ(1ul, pm->update_back_off_mult_);
  expect_callback_to_be_called_ = false;
  pm->store_state_map_ = std::move(store_state_map_);
  pm->IssueUpdateRequest();

  EXPECT_FALSE(pm->IsUpdateScheduled());

  // Failed request status should result in error.
  pm->OnURLLoaderCompleteInternal(net::ERR_CONNECTION_RESET, 0, std::string());

  // Should have recorded one error, but back off multiplier is unchanged.
  EXPECT_EQ(1ul, pm->update_error_count_);
  EXPECT_EQ(1ul, pm->update_back_off_mult_);
  EXPECT_TRUE(pm->IsUpdateScheduled());
}

TEST_F(V4UpdateProtocolManagerTest, TestGetUpdatesErrorHandlingResponseCode) {
  scoped_refptr<base::TestSimpleTaskRunner> runner(
      new base::TestSimpleTaskRunner());
  base::ThreadTaskRunnerHandle runner_handler(runner);
  const std::vector<ListUpdateResponse> expected_lurs;
  std::unique_ptr<V4UpdateProtocolManager> pm(
      CreateProtocolManager(expected_lurs));
  runner->ClearPendingTasks();

  // Initial state. No errors.
  EXPECT_EQ(0ul, pm->update_error_count_);
  EXPECT_EQ(1ul, pm->update_back_off_mult_);
  expect_callback_to_be_called_ = false;
  pm->store_state_map_ = std::move(store_state_map_);
  pm->IssueUpdateRequest();

  EXPECT_FALSE(pm->IsUpdateScheduled());

  // Response code of anything other than 200 should result in error.
  pm->OnURLLoaderCompleteInternal(net::HTTP_NO_CONTENT, 0, std::string());

  // Should have recorded one error, but back off multiplier is unchanged.
  EXPECT_EQ(1ul, pm->update_error_count_);
  EXPECT_EQ(1ul, pm->update_back_off_mult_);
  EXPECT_TRUE(pm->IsUpdateScheduled());
}

TEST_F(V4UpdateProtocolManagerTest, TestGetUpdatesNoError) {
  scoped_refptr<base::TestSimpleTaskRunner> runner(
      new base::TestSimpleTaskRunner());
  base::ThreadTaskRunnerHandle runner_handler(runner);
  std::vector<ListUpdateResponse> expected_lurs;
  SetupExpectedListUpdateResponse(&expected_lurs);
  std::unique_ptr<V4UpdateProtocolManager> pm(
      CreateProtocolManager(expected_lurs));
  runner->ClearPendingTasks();

  // Initial state. No errors.
  EXPECT_EQ(0ul, pm->update_error_count_);
  EXPECT_EQ(1ul, pm->update_back_off_mult_);
  expect_callback_to_be_called_ = true;
  pm->store_state_map_ = std::move(store_state_map_);
  pm->IssueUpdateRequest();

  EXPECT_FALSE(pm->IsUpdateScheduled());

  pm->OnURLLoaderCompleteInternal(net::OK, net::HTTP_OK,
                                  GetExpectedV4UpdateResponse(expected_lurs));

  // No error, back off multiplier is unchanged.
  EXPECT_EQ(0ul, pm->update_error_count_);
  EXPECT_EQ(1ul, pm->update_back_off_mult_);
  EXPECT_FALSE(pm->IsUpdateScheduled());
}

TEST_F(V4UpdateProtocolManagerTest, TestGetUpdatesWithOneBackoff) {
  scoped_refptr<base::TestSimpleTaskRunner> runner(
      new base::TestSimpleTaskRunner());
  base::ThreadTaskRunnerHandle runner_handler(runner);
  std::vector<ListUpdateResponse> expected_lurs;
  SetupExpectedListUpdateResponse(&expected_lurs);
  std::unique_ptr<V4UpdateProtocolManager> pm(
      CreateProtocolManager(expected_lurs));
  runner->ClearPendingTasks();

  // Initial state. No errors.
  EXPECT_EQ(0ul, pm->update_error_count_);
  EXPECT_EQ(1ul, pm->update_back_off_mult_);
  expect_callback_to_be_called_ = false;
  pm->store_state_map_ = std::move(store_state_map_);
  pm->IssueUpdateRequest();

  EXPECT_FALSE(pm->IsUpdateScheduled());

  // Response code of anything other than 200 should result in error.
  pm->OnURLLoaderCompleteInternal(net::HTTP_NO_CONTENT, 0, std::string());

  // Should have recorded one error, but back off multiplier is unchanged.
  EXPECT_EQ(1ul, pm->update_error_count_);
  EXPECT_EQ(1ul, pm->update_back_off_mult_);
  EXPECT_TRUE(pm->IsUpdateScheduled());

  // Retry, now no backoff.
  expect_callback_to_be_called_ = true;
  // Call RunPendingTasks to ensure that the request is sent after backoff.
  runner->RunPendingTasks();

  pm->OnURLLoaderCompleteInternal(net::OK, net::HTTP_OK,
                                  GetExpectedV4UpdateResponse(expected_lurs));

  // No error, back off multiplier is unchanged.
  EXPECT_EQ(0ul, pm->update_error_count_);
  EXPECT_EQ(1ul, pm->update_back_off_mult_);
  EXPECT_FALSE(pm->IsUpdateScheduled());
}

TEST_F(V4UpdateProtocolManagerTest, TestBase64EncodingUsesUrlEncoding) {
  // NOTE(vakh): I handpicked this value for state by generating random strings
  // and picked the one that leads to a '-' in the base64 url encoded request
  // output.
  store_state_map_->clear();
  (*store_state_map_)[ListIdentifier(LINUX_PLATFORM, URL, MALWARE_THREAT)] =
      "h8xfYqY>:R";
  std::unique_ptr<V4UpdateProtocolManager> pm(
      CreateProtocolManager(std::vector<ListUpdateResponse>({})));
  pm->store_state_map_ = std::move(store_state_map_);

  std::string encoded_request_with_minus =
      pm->GetBase64SerializedUpdateRequestProto();
  EXPECT_EQ("Cg8KCHVuaXR0ZXN0EgMxLjAaGAgBEAIaCmg4eGZZcVk-OlIiBCABIAIoASICCAE=",
            encoded_request_with_minus);

  // TODO(vakh): Add a similar test for underscore for completeness, although
  // the '-' case is sufficient to prove that we are using URL encoding.
}

TEST_F(V4UpdateProtocolManagerTest, TestDisableAutoUpdates) {
  scoped_refptr<base::TestSimpleTaskRunner> runner(
      new base::TestSimpleTaskRunner());
  base::ThreadTaskRunnerHandle runner_handler(runner);
  std::unique_ptr<V4UpdateProtocolManager> pm(CreateProtocolManager(
      std::vector<ListUpdateResponse>(), true /* disable_auto_update */));

  // Initial state. No errors.
  pm->ScheduleNextUpdate(std::move(store_state_map_));
  EXPECT_FALSE(pm->IsUpdateScheduled());

  runner->RunPendingTasks();
  EXPECT_FALSE(pm->IsUpdateScheduled());

  DCHECK(!pm->request_);
}

TEST_F(V4UpdateProtocolManagerTest, TestGetUpdatesHasTimeout) {
  scoped_refptr<base::TestSimpleTaskRunner> runner(
      new base::TestSimpleTaskRunner());
  base::ThreadTaskRunnerHandle runner_handler(runner);
  std::vector<ListUpdateResponse> expected_lurs;
  SetupExpectedListUpdateResponse(&expected_lurs);
  std::unique_ptr<V4UpdateProtocolManager> pm(
      CreateProtocolManager(expected_lurs));
  runner->ClearPendingTasks();

  // Initial state. No errors.
  EXPECT_EQ(0ul, pm->update_error_count_);
  EXPECT_EQ(1ul, pm->update_back_off_mult_);
  expect_callback_to_be_called_ = true;
  pm->store_state_map_ = std::move(store_state_map_);
  pm->IssueUpdateRequest();

  // Don't set anything on the loader. Let it time out.
  runner->RunPendingTasks();

  // Now wait for the next request to be scheduled.
  runner->RunPendingTasks();

  // Should have recorded one error, but back off multiplier is unchanged.
  EXPECT_EQ(1ul, pm->update_error_count_);
  EXPECT_EQ(1ul, pm->update_back_off_mult_);

  // There should be another fetcher now.
  pm->OnURLLoaderCompleteInternal(net::OK, net::HTTP_OK,
                                  GetExpectedV4UpdateResponse(expected_lurs));

  // No error, back off multiplier is unchanged.
  EXPECT_EQ(0ul, pm->update_error_count_);
  EXPECT_EQ(1ul, pm->update_back_off_mult_);
}

TEST_F(V4UpdateProtocolManagerTest, TestExtendedReportingLevelIncluded) {
  store_state_map_->clear();
  (*store_state_map_)[ListIdentifier(LINUX_PLATFORM, URL, MALWARE_THREAT)] =
      "state";
  std::string base = "Cg8KCHVuaXR0ZXN0EgMxLjAaEwgBEAIaBXN0YXRlIgQgASACKAEiAgg";

  std::unique_ptr<V4UpdateProtocolManager> pm_with_off(CreateProtocolManager(
      std::vector<ListUpdateResponse>({}), false, SBER_LEVEL_OFF));
  pm_with_off->store_state_map_ = std::move(store_state_map_);
  EXPECT_EQ(base + "B", pm_with_off->GetBase64SerializedUpdateRequestProto());

  std::unique_ptr<V4UpdateProtocolManager> pm_with_legacy(CreateProtocolManager(
      std::vector<ListUpdateResponse>({}), false, SBER_LEVEL_LEGACY));
  pm_with_legacy->store_state_map_ = std::move(pm_with_off->store_state_map_);
  EXPECT_EQ(base + "C",
            pm_with_legacy->GetBase64SerializedUpdateRequestProto());

  std::unique_ptr<V4UpdateProtocolManager> pm_with_scout(CreateProtocolManager(
      std::vector<ListUpdateResponse>({}), false, SBER_LEVEL_SCOUT));
  pm_with_scout->store_state_map_ = std::move(pm_with_legacy->store_state_map_);
  EXPECT_EQ(base + "D", pm_with_scout->GetBase64SerializedUpdateRequestProto());
}

}  // namespace safe_browsing
