chromium/chromeos/ash/services/secure_channel/ble_synchronizer_unittest.cc

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/services/secure_channel/ble_synchronizer.h"

#include <memory>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/simple_test_clock.h"
#include "base/test/task_environment.h"
#include "base/test/test_simple_task_runner.h"
#include "base/timer/mock_timer.h"
#include "chromeos/ash/services/secure_channel/data_with_timestamp.h"
#include "chromeos/ash/services/secure_channel/public/cpp/shared/ble_constants.h"
#include "device/bluetooth/bluetooth_advertisement.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/bluetooth/test/mock_bluetooth_advertisement.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash::secure_channel {

namespace {

using ::testing::_;
using ::testing::Invoke;
using ::testing::NiceMock;

const char kId1[] = "id1";
const char kId2[] = "id2";
const char kId3[] = "id3";

int64_t kTimeBetweenEachCommandMs = 200;

struct RegisterAdvertisementArgs {
  RegisterAdvertisementArgs(
      const device::BluetoothAdvertisement::UUIDList& service_uuids,
      device::BluetoothAdapter::CreateAdvertisementCallback callback,
      device::BluetoothAdapter::AdvertisementErrorCallback error_callback)
      : service_uuids(service_uuids),
        callback(std::move(callback)),
        error_callback(std::move(error_callback)) {}

  device::BluetoothAdvertisement::UUIDList service_uuids;
  device::BluetoothAdapter::CreateAdvertisementCallback callback;
  device::BluetoothAdapter::AdvertisementErrorCallback error_callback;
};

struct UnregisterAdvertisementArgs {
  UnregisterAdvertisementArgs(
      device::BluetoothAdvertisement::SuccessCallback callback,
      device::BluetoothAdvertisement::ErrorCallback error_callback)
      : callback(std::move(callback)),
        error_callback(std::move(error_callback)) {}

  device::BluetoothAdvertisement::SuccessCallback callback;
  device::BluetoothAdvertisement::ErrorCallback error_callback;
};

struct StartDiscoverySessionArgs {
  StartDiscoverySessionArgs(
      base::OnceClosure callback,
      device::BluetoothAdapter::ErrorCallback error_callback)
      : callback(std::move(callback)),
        error_callback(std::move(error_callback)) {}

  base::OnceClosure callback;
  device::BluetoothAdapter::ErrorCallback error_callback;
};

struct StopDiscoverySessionArgs {
  StopDiscoverySessionArgs(
      base::OnceClosure callback,
      device::BluetoothDiscoverySession::ErrorCallback error_callback)
      : callback(std::move(callback)),
        error_callback(std::move(error_callback)) {}

  base::OnceClosure callback;
  device::BluetoothDiscoverySession::ErrorCallback error_callback;
};

class MockBluetoothAdapterWithAdvertisements
    : public device::MockBluetoothAdapter {
 public:
  MockBluetoothAdapterWithAdvertisements() : MockBluetoothAdapter() {}

  MOCK_METHOD1(RegisterAdvertisementWithArgsStruct,
               void(RegisterAdvertisementArgs*));

  void RegisterAdvertisement(
      std::unique_ptr<device::BluetoothAdvertisement::Data> advertisement_data,
      device::BluetoothAdapter::CreateAdvertisementCallback callback,
      device::BluetoothAdapter::AdvertisementErrorCallback error_callback)
      override {
    RegisterAdvertisementWithArgsStruct(new RegisterAdvertisementArgs(
        *advertisement_data->service_uuids(), std::move(callback),
        std::move(error_callback)));
  }

 protected:
  ~MockBluetoothAdapterWithAdvertisements() override = default;
};

class FakeBluetoothAdvertisement : public device::BluetoothAdvertisement {
 public:
  // |unregister_callback| should be called with the callbacks passed to
  // Unregister() whenever an Unregister() call occurs.
  FakeBluetoothAdvertisement(
      const base::RepeatingCallback<void(
          device::BluetoothAdvertisement::SuccessCallback,
          device::BluetoothAdvertisement::ErrorCallback)>& unregister_callback)
      : unregister_callback_(unregister_callback) {}

  FakeBluetoothAdvertisement(const FakeBluetoothAdvertisement&) = delete;
  FakeBluetoothAdvertisement& operator=(const FakeBluetoothAdvertisement&) =
      delete;

  // BluetoothAdvertisement:
  void Unregister(
      device::BluetoothAdvertisement::SuccessCallback success_callback,
      device::BluetoothAdvertisement::ErrorCallback error_callback) override {
    unregister_callback_.Run(std::move(success_callback),
                             std::move(error_callback));
  }

 private:
  ~FakeBluetoothAdvertisement() override = default;

  base::RepeatingCallback<void(device::BluetoothAdvertisement::SuccessCallback,
                               device::BluetoothAdvertisement::ErrorCallback)>
      unregister_callback_;
};

// Creates a UUIDList with one element of value |id|.
device::BluetoothAdvertisement::UUIDList CreateUUIDList(const std::string& id) {
  return device::BluetoothAdvertisement::UUIDList(1u, id);
}

// Creates advertisement data with a UUID list with one element of value |id|.
std::unique_ptr<device::BluetoothAdvertisement::Data> GenerateAdvertisementData(
    const std::string& id) {
  auto data = std::make_unique<device::BluetoothAdvertisement::Data>(
      device::BluetoothAdvertisement::AdvertisementType::
          ADVERTISEMENT_TYPE_PERIPHERAL);
  data->set_service_uuids(CreateUUIDList(id));
  return data;
}

}  // namespace

class SecureChannelBleSynchronizerTest : public testing::Test {
 public:
  SecureChannelBleSynchronizerTest(const SecureChannelBleSynchronizerTest&) =
      delete;
  SecureChannelBleSynchronizerTest& operator=(
      const SecureChannelBleSynchronizerTest&) = delete;

 protected:
  SecureChannelBleSynchronizerTest()
      : fake_advertisement_(base::MakeRefCounted<FakeBluetoothAdvertisement>(
            base::BindRepeating(
                &SecureChannelBleSynchronizerTest::OnUnregisterCalled,
                base::Unretained(this)))) {}

  void SetUp() override {
    num_register_success_ = 0;
    num_register_error_ = 0;
    num_unregister_success_ = 0;
    num_unregister_error_ = 0;
    num_start_success_ = 0;
    num_start_error_ = 0;
    num_stop_success_ = 0;
    num_stop_error_ = 0;
    register_args_list_.clear();

    mock_adapter_ = base::MakeRefCounted<
        NiceMock<MockBluetoothAdapterWithAdvertisements>>();
    ON_CALL(*mock_adapter_, RegisterAdvertisementWithArgsStruct(_))
        .WillByDefault(Invoke(
            this,
            &SecureChannelBleSynchronizerTest::OnAdapterRegisterAdvertisement));
    ON_CALL(*mock_adapter_, StartScanWithFilter_(_, _))
        .WillByDefault(Invoke(
            this, &SecureChannelBleSynchronizerTest::OnAdapterStartScan));
    ON_CALL(*mock_adapter_, StopScan(_))
        .WillByDefault(
            Invoke(this, &SecureChannelBleSynchronizerTest::OnStopScan));

    mock_timer_ = new base::MockOneShotTimer();

    test_clock_.Advance(TimeDeltaMillis(kTimeBetweenEachCommandMs));
    test_task_runner_ = base::MakeRefCounted<base::TestSimpleTaskRunner>();

    synchronizer_ = BleSynchronizer::Factory::Create(mock_adapter_);

    BleSynchronizer* derived_type =
        static_cast<BleSynchronizer*>(synchronizer_.get());
    derived_type->SetTestDoubles(base::WrapUnique(mock_timer_.get()),
                                 &test_clock_, test_task_runner_);
  }

  base::TimeDelta TimeDeltaMillis(int64_t num_millis) {
    return base::Milliseconds(num_millis);
  }

  void OnAdapterRegisterAdvertisement(RegisterAdvertisementArgs* args) {
    register_args_list_.emplace_back(base::WrapUnique(args));
  }

  void OnAdapterStartScan(
      const device::BluetoothDiscoveryFilter* discovery_filter,
      device::BluetoothAdapter::DiscoverySessionResultCallback& callback) {
    EXPECT_EQ(device::BluetoothTransport::BLUETOOTH_TRANSPORT_LE,
              discovery_filter->GetTransport());
    auto split_callback = base::SplitOnceCallback(std::move(callback));
    start_discovery_args_list_.emplace_back(
        base::WrapUnique(new StartDiscoverySessionArgs(
            base::BindOnce(
                std::move(split_callback.first), /*is_error=*/false,
                device::UMABluetoothDiscoverySessionOutcome::SUCCESS),
            base::BindOnce(
                std::move(split_callback.second), /*is_error=*/true,
                device::UMABluetoothDiscoverySessionOutcome::UNKNOWN))));
  }

  void RegisterAdvertisement(const std::string& id) {
    synchronizer_->RegisterAdvertisement(
        GenerateAdvertisementData(id),
        base::BindOnce(
            &SecureChannelBleSynchronizerTest::OnAdvertisementRegistered,
            base::Unretained(this)),
        base::BindOnce(
            &SecureChannelBleSynchronizerTest::OnErrorRegisteringAdvertisement,
            base::Unretained(this)));
  }

  void InvokeRegisterCallback(bool success,
                              const std::string& expected_id,
                              size_t reg_arg_index,
                              size_t expected_registration_result_count) {
    EXPECT_TRUE(register_args_list_.size() >= reg_arg_index);
    EXPECT_EQ(CreateUUIDList(expected_id),
              register_args_list_[reg_arg_index]->service_uuids);

    if (success) {
      std::move(register_args_list_[reg_arg_index]->callback)
          .Run(base::MakeRefCounted<device::MockBluetoothAdvertisement>());
    } else {
      std::move(register_args_list_[reg_arg_index]->error_callback)
          .Run(device::BluetoothAdvertisement::ErrorCode::
                   INVALID_ADVERTISEMENT_ERROR_CODE);
    }

    // Reset to make sure that this callback is never double-invoked.
    register_args_list_[reg_arg_index].reset();
    test_task_runner_->RunUntilIdle();
  }

  void OnAdvertisementRegistered(
      scoped_refptr<device::BluetoothAdvertisement> advertisement) {
    ++num_register_success_;
  }

  void OnErrorRegisteringAdvertisement(
      device::BluetoothAdvertisement::ErrorCode error_code) {
    ++num_register_error_;
  }

  void UnregisterAdvertisement() {
    synchronizer_->UnregisterAdvertisement(
        fake_advertisement_,
        base::BindOnce(
            &SecureChannelBleSynchronizerTest::OnAdvertisementUnregistered,
            base::Unretained(this)),
        base::BindOnce(&SecureChannelBleSynchronizerTest::
                           OnErrorUnregisteringAdvertisement,
                       base::Unretained(this)));
  }

  // If |success| is false, the error code defaults to
  // INVALID_ADVERTISEMENT_ERROR_CODE unless otherwise specified. If |success|
  // is true, |error_code| is simply ignored.
  void InvokeUnregisterCallback(
      bool success,
      size_t unreg_arg_index,
      size_t expected_unregistration_result_count,
      device::BluetoothAdvertisement::ErrorCode error_code = device::
          BluetoothAdvertisement::ErrorCode::INVALID_ADVERTISEMENT_ERROR_CODE) {
    EXPECT_TRUE(unregister_args_list_.size() >= unreg_arg_index);

    if (success) {
      std::move(unregister_args_list_[unreg_arg_index]->callback).Run();
    } else {
      std::move(unregister_args_list_[unreg_arg_index]->error_callback)
          .Run(error_code);
    }

    // Reset to make sure that this callback is never double-invoked.
    unregister_args_list_[unreg_arg_index].reset();
    test_task_runner_->RunUntilIdle();
  }

  void OnAdvertisementUnregistered() { ++num_unregister_success_; }

  void OnErrorUnregisteringAdvertisement(
      device::BluetoothAdvertisement::ErrorCode error_code) {
    ++num_unregister_error_;
  }

  void StartDiscoverySession() {
    synchronizer_->StartDiscoverySession(
        base::BindOnce(
            &SecureChannelBleSynchronizerTest::OnDiscoverySessionStarted,
            base::Unretained(this)),
        base::BindOnce(
            &SecureChannelBleSynchronizerTest::OnErrorStartingDiscoverySession,
            base::Unretained(this)));
  }

  void InvokeStartDiscoveryCallback(
      bool success,
      size_t start_arg_index,
      size_t expected_start_discovery_result_count) {
    EXPECT_TRUE(start_discovery_args_list_.size() >= start_arg_index);

    if (success) {
      std::move(start_discovery_args_list_[start_arg_index]->callback).Run();
    } else {
      std::move(start_discovery_args_list_[start_arg_index]->error_callback)
          .Run();
    }

    histogram_tester_.ExpectUniqueSample(
        "InstantTethering.BluetoothDiscoverySessionStarted", success ? 1 : 0,
        expected_start_discovery_result_count);

    // Reset to make sure that this callback is never double-invoked.
    start_discovery_args_list_[start_arg_index].reset();
    test_task_runner_->RunUntilIdle();
  }

  void OnDiscoverySessionStarted(
      std::unique_ptr<device::BluetoothDiscoverySession> discovery_session) {
    discovery_session_ = std::move(discovery_session);
    discovery_session_weak_ptr_factory_ = std::make_unique<
        base::WeakPtrFactory<device::BluetoothDiscoverySession>>(
        discovery_session_.get());
    ++num_start_success_;
  }

  void OnErrorStartingDiscoverySession() { ++num_start_error_; }

  void StopDiscoverySession(
      base::WeakPtr<device::BluetoothDiscoverySession> discovery_session) {
    synchronizer_->StopDiscoverySession(
        discovery_session,
        base::BindOnce(
            &SecureChannelBleSynchronizerTest::OnDiscoverySessionStopped,
            base::Unretained(this)),
        base::BindOnce(
            &SecureChannelBleSynchronizerTest::OnErrorStoppingDiscoverySession,
            base::Unretained(this)));
  }

  void InvokeStopDiscoveryCallback(
      size_t stop_arg_index,
      size_t expected_stop_discovery_result_count) {
    EXPECT_TRUE(stop_discovery_args_list_.size() >= stop_arg_index);

    std::move(stop_discovery_args_list_[stop_arg_index]->callback).Run();

    histogram_tester_.ExpectUniqueSample(
        "InstantTethering.BluetoothDiscoverySessionStopped", 1,
        expected_stop_discovery_result_count);

    // Reset to make sure that this callback is never double-invoked.
    stop_discovery_args_list_[stop_arg_index].reset();
    test_task_runner_->RunUntilIdle();
  }

  void OnDiscoverySessionStopped() {
    discovery_session_.reset();
    ++num_stop_success_;
  }

  void OnErrorStoppingDiscoverySession() { ++num_stop_error_; }

  void FireTimer() {
    EXPECT_TRUE(mock_timer_->IsRunning());
    mock_timer_->Fire();
  }

  void OnUnregisterCalled(
      device::BluetoothAdvertisement::SuccessCallback callback,
      device::BluetoothAdvertisement::ErrorCallback error_callback) {
    unregister_args_list_.push_back(
        std::make_unique<UnregisterAdvertisementArgs>(
            std::move(callback), std::move(error_callback)));
  }

  void OnStopScan(
      device::BluetoothAdapter::DiscoverySessionResultCallback callback) {
    auto split_callback = base::SplitOnceCallback(std::move(callback));
    stop_discovery_args_list_.emplace_back(
        base::WrapUnique(new StopDiscoverySessionArgs(
            base::BindOnce(
                std::move(split_callback.first), /*is_error=*/false,
                device::UMABluetoothDiscoverySessionOutcome::SUCCESS),
            base::BindOnce(
                std::move(split_callback.second),
                /*is_error=*/true,
                device::UMABluetoothDiscoverySessionOutcome::UNKNOWN))));
  }

  base::test::TaskEnvironment task_environment_;
  const scoped_refptr<FakeBluetoothAdvertisement> fake_advertisement_;

  scoped_refptr<NiceMock<MockBluetoothAdapterWithAdvertisements>> mock_adapter_;

  raw_ptr<base::MockOneShotTimer, DanglingUntriaged> mock_timer_;
  base::SimpleTestClock test_clock_;
  scoped_refptr<base::TestSimpleTaskRunner> test_task_runner_;

  std::vector<std::unique_ptr<RegisterAdvertisementArgs>> register_args_list_;
  std::vector<std::unique_ptr<UnregisterAdvertisementArgs>>
      unregister_args_list_;
  std::vector<std::unique_ptr<StartDiscoverySessionArgs>>
      start_discovery_args_list_;
  std::vector<std::unique_ptr<StopDiscoverySessionArgs>>
      stop_discovery_args_list_;

  std::unique_ptr<device::BluetoothDiscoverySession> discovery_session_;
  std::unique_ptr<base::WeakPtrFactory<device::BluetoothDiscoverySession>>
      discovery_session_weak_ptr_factory_;

  int num_register_success_;
  int num_register_error_;
  int num_unregister_success_;
  int num_unregister_error_;
  int num_start_success_;
  int num_start_error_;
  int num_stop_success_;
  int num_stop_error_;

  bool stopped_callback_called_;

  std::unique_ptr<BleSynchronizerBase> synchronizer_;

  base::HistogramTester histogram_tester_;
};

TEST_F(SecureChannelBleSynchronizerTest, TestRegisterSuccess) {
  RegisterAdvertisement(kId1);
  InvokeRegisterCallback(true /* success */, kId1, 0u /* reg_arg_index */,
                         1u /* expected_registration_result_count */);
  EXPECT_EQ(1, num_register_success_);
}

TEST_F(SecureChannelBleSynchronizerTest, TestRegisterError) {
  RegisterAdvertisement(kId1);
  InvokeRegisterCallback(false /* success */, kId1, 0u /* reg_arg_index */,
                         1u /* expected_registration_result_count */);
  EXPECT_EQ(1, num_register_error_);
}

TEST_F(SecureChannelBleSynchronizerTest, TestUnregisterSuccess) {
  UnregisterAdvertisement();
  InvokeUnregisterCallback(true /* success */, 0u /* reg_arg_index */,
                           1 /* expected_unregistration_result_count */);
  EXPECT_EQ(1, num_unregister_success_);
}

TEST_F(SecureChannelBleSynchronizerTest, TestUnregisterError) {
  UnregisterAdvertisement();
  InvokeUnregisterCallback(false /* success */, 0u /* reg_arg_index */,
                           1 /* expected_unregistration_result_count */);
  EXPECT_EQ(1, num_unregister_error_);
}

TEST_F(SecureChannelBleSynchronizerTest,
       TestUnregisterError_AdvertisementDoesNotExist) {
  UnregisterAdvertisement();
  InvokeUnregisterCallback(false /* success */, 0u /* reg_arg_index */,
                           1 /* expected_unregistration_result_count */,
                           device::BluetoothAdvertisement::ErrorCode::
                               ERROR_ADVERTISEMENT_DOES_NOT_EXIST);
  EXPECT_EQ(1, num_unregister_success_);
  EXPECT_EQ(0, num_unregister_error_);
}

TEST_F(SecureChannelBleSynchronizerTest, TestStartSuccess) {
  StartDiscoverySession();
  InvokeStartDiscoveryCallback(true /* success */, 0u /* reg_arg_index */,
                               1 /* expected_start_discovery_result_count */);
  EXPECT_EQ(1, num_start_success_);
}

TEST_F(SecureChannelBleSynchronizerTest, TestStartError) {
  StartDiscoverySession();
  InvokeStartDiscoveryCallback(false /* success */, 0u /* reg_arg_index */,
                               1 /* expected_start_discovery_result_count */);
  EXPECT_EQ(1, num_start_error_);
}

TEST_F(SecureChannelBleSynchronizerTest, TestStopSuccess) {
  StartDiscoverySession();
  InvokeStartDiscoveryCallback(true /* success */, 0u /* reg_arg_index */,
                               1 /* expected_start_discovery_result_count */);
  EXPECT_EQ(1, num_start_success_);

  StopDiscoverySession(discovery_session_weak_ptr_factory_->GetWeakPtr());

  // Advance the clock and fire the timer. This should result in the next
  // command being executed.
  test_clock_.Advance(TimeDeltaMillis(kTimeBetweenEachCommandMs));
  mock_timer_->Fire();

  InvokeStopDiscoveryCallback(0u /* unreg_arg_index */,
                              1 /* expected_stop_discovery_result_count */);
  EXPECT_EQ(1, num_stop_success_);
}

TEST_F(SecureChannelBleSynchronizerTest, TestStop_DeletedDiscoverySession) {
  // Simulate an invalidated WeakPtr being processed.
  StopDiscoverySession(base::WeakPtr<device::BluetoothDiscoverySession>());
  RegisterAdvertisement(kId1);

  // Stop() should not have been called.
  EXPECT_TRUE(stop_discovery_args_list_.empty());

  // The RegisterAdvertisement() command should be sent without the need for a
  // delay, since the previous command was not actually sent.
  InvokeRegisterCallback(true /* success */, kId1, 0u /* reg_arg_index */,
                         1u /* expected_registration_result_count */);
  EXPECT_EQ(1, num_register_success_);
}

TEST_F(SecureChannelBleSynchronizerTest, TestThrottling) {
  RegisterAdvertisement(kId1);
  InvokeRegisterCallback(true /* success */, kId1, 0u /* reg_arg_index */,
                         1u /* expected_registration_result_count */);
  EXPECT_EQ(1, num_register_success_);

  // Advance to one millisecond before the limit.
  test_clock_.Advance(TimeDeltaMillis(kTimeBetweenEachCommandMs - 1));
  UnregisterAdvertisement();

  // Should still be empty since it should have been throttled, and the timer
  // should be running.
  EXPECT_TRUE(unregister_args_list_.empty());
  EXPECT_TRUE(mock_timer_->IsRunning());

  // Advance the clock and fire the timer. This should result in the next
  // command being executed.
  test_clock_.Advance(TimeDeltaMillis(kTimeBetweenEachCommandMs));
  mock_timer_->Fire();

  InvokeUnregisterCallback(true /* success */, 0u /* reg_arg_index */,
                           1 /* expected_unregistration_result_count */);
  EXPECT_EQ(1, num_unregister_success_);

  // Now, register 2 advertisements at the same time.
  RegisterAdvertisement(kId2);
  RegisterAdvertisement(kId3);

  // Should still only have the original register command..
  EXPECT_EQ(1u, register_args_list_.size());

  // Advance the clock and fire the timer. This should result in the next
  // command being executed.
  test_clock_.Advance(TimeDeltaMillis(kTimeBetweenEachCommandMs));
  mock_timer_->Fire();

  EXPECT_EQ(2u, register_args_list_.size());
  InvokeRegisterCallback(false /* success */, kId2, 1u /* reg_arg_index */,
                         1u /* expected_registration_result_count */);
  EXPECT_EQ(1, num_register_error_);

  // Advance the clock and fire the timer. This should result in the next
  // command being executed.
  test_clock_.Advance(TimeDeltaMillis(kTimeBetweenEachCommandMs));
  mock_timer_->Fire();

  EXPECT_EQ(3u, register_args_list_.size());
  InvokeRegisterCallback(true /* success */, kId3, 2u /* reg_arg_index */,
                         2u /* expected_registration_result_count */);
  EXPECT_EQ(2, num_register_success_);

  // Advance the clock before doing anything else. The next request should not
  // be throttled.
  EXPECT_FALSE(mock_timer_->IsRunning());
  test_clock_.Advance(TimeDeltaMillis(kTimeBetweenEachCommandMs));

  UnregisterAdvertisement();
  InvokeUnregisterCallback(false /* success */, 1u /* reg_arg_index */,
                           1 /* expected_unregistration_result_count */);
  EXPECT_EQ(1, num_unregister_error_);
}

}  // namespace ash::secure_channel