blob: 40abea976adc7e50524e5cd08ef36dc9bca4d0df [file] [log] [blame]
// Copyright 2018 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 "chromeos/services/secure_channel/ble_scanner_impl.h"
#include <algorithm>
#include <iterator>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/callback_forward.h"
#include "base/memory/ptr_util.h"
#include "base/test/bind_test_util.h"
#include "chromeos/components/multidevice/remote_device_test_util.h"
#include "chromeos/services/secure_channel/ble_constants.h"
#include "chromeos/services/secure_channel/connection_role.h"
#include "chromeos/services/secure_channel/fake_ble_scanner.h"
#include "chromeos/services/secure_channel/fake_ble_synchronizer.h"
#include "chromeos/services/secure_channel/fake_bluetooth_helper.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/bluetooth/test/mock_bluetooth_device.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace secure_channel {
namespace {
// Extends device::MockBluetoothDevice, adding the ability to set service data
// to be returned.
class FakeBluetoothDevice : public device::MockBluetoothDevice {
public:
FakeBluetoothDevice(const std::string& service_data,
device::MockBluetoothAdapter* adapter)
: device::MockBluetoothDevice(adapter,
0u /* bluetooth_class */,
"name",
"address",
false /* paired */,
false /* connected */) {
// Convert |service_data| from a std::string to a std::vector<uint8_t>.
std::transform(service_data.begin(), service_data.end(),
std::back_inserter(service_data_vector_),
[](char character) { return character; });
}
const std::vector<uint8_t>* service_data() { return &service_data_vector_; }
private:
std::vector<uint8_t> service_data_vector_;
DISALLOW_COPY_AND_ASSIGN(FakeBluetoothDevice);
};
std::vector<std::pair<ConnectionMedium, ConnectionRole>>
CreateSingleBleScanResult(bool is_background_advertisement) {
return std::vector<std::pair<ConnectionMedium, ConnectionRole>>{
{ConnectionMedium::kBluetoothLowEnergy,
is_background_advertisement ? ConnectionRole::kListenerRole
: ConnectionRole::kInitiatorRole}};
}
} // namespace
class SecureChannelBleScannerImplTest : public testing::Test {
protected:
class FakeServiceDataProvider : public BleScannerImpl::ServiceDataProvider {
public:
FakeServiceDataProvider() = default;
~FakeServiceDataProvider() override = default;
// ServiceDataProvider:
const std::vector<uint8_t>* ExtractProximityAuthServiceData(
device::BluetoothDevice* bluetooth_device) override {
FakeBluetoothDevice* mock_device =
static_cast<FakeBluetoothDevice*>(bluetooth_device);
return mock_device->service_data();
}
};
SecureChannelBleScannerImplTest()
: test_devices_(multidevice::CreateRemoteDeviceRefListForTest(3)) {}
~SecureChannelBleScannerImplTest() override = default;
// testing::Test:
void SetUp() override {
fake_delegate_ = std::make_unique<FakeBleScannerObserver>();
fake_bluetooth_helper_ = std::make_unique<FakeBluetoothHelper>();
fake_ble_synchronizer_ = std::make_unique<FakeBleSynchronizer>();
mock_adapter_ =
base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();
ble_scanner_ = BleScannerImpl::Factory::Create(fake_bluetooth_helper_.get(),
fake_ble_synchronizer_.get(),
mock_adapter_);
ble_scanner_->AddObserver(fake_delegate_.get());
auto fake_service_data_provider =
std::make_unique<FakeServiceDataProvider>();
fake_service_data_provider_ = fake_service_data_provider.get();
BleScannerImpl* ble_scanner_derived =
static_cast<BleScannerImpl*>(ble_scanner_.get());
ble_scanner_derived->SetServiceDataProviderForTesting(
std::move(fake_service_data_provider));
ON_CALL(*mock_adapter_, StartScanWithFilter_(testing::_, testing::_))
.WillByDefault(testing::Invoke(
[](const device::BluetoothDiscoveryFilter* discovery_filter,
device::BluetoothAdapter::DiscoverySessionResultCallback&
callback) {
std::move(callback).Run(
/*is_error=*/false,
device::UMABluetoothDiscoverySessionOutcome::SUCCESS);
}));
ON_CALL(*mock_adapter_, StopScan(testing::_))
.WillByDefault(testing::Invoke(
[](device::BluetoothAdapter::DiscoverySessionResultCallback
callback) {
std::move(callback).Run(
/*is_error=*/false,
device::UMABluetoothDiscoverySessionOutcome::SUCCESS);
}));
}
void TearDown() override {
ble_scanner_->RemoveObserver(fake_delegate_.get());
}
void AddScanRequest(const ConnectionAttemptDetails& scan_filter) {
EXPECT_FALSE(ble_scanner_->HasScanRequest(scan_filter));
ble_scanner_->AddScanRequest(scan_filter);
EXPECT_TRUE(ble_scanner_->HasScanRequest(scan_filter));
}
// StartDiscoverySession in the mock adapter mostly for the purpose of
// creating a DiscoverySession.
void StartDiscoverySession() {
mock_adapter_->StartDiscoverySession(
base::BindLambdaForTesting(
[&](std::unique_ptr<device::BluetoothDiscoverySession>
discovery_session) {
discovery_session_ = std::move(discovery_session);
discovery_session_weak_ptr_ = discovery_session_->GetWeakPtr();
}),
base::RepeatingClosure());
}
void RemoveScanRequest(const ConnectionAttemptDetails& scan_filter) {
EXPECT_TRUE(ble_scanner_->HasScanRequest(scan_filter));
ble_scanner_->RemoveScanRequest(scan_filter);
EXPECT_FALSE(ble_scanner_->HasScanRequest(scan_filter));
}
void ProcessScanResultAndVerifyNoDeviceIdentified(
const std::string& service_data) {
const std::vector<FakeBleScannerObserver::Result>& results =
fake_delegate_->handled_scan_results();
size_t num_results_before_call = results.size();
SimulateScanResult(service_data);
EXPECT_EQ(num_results_before_call, results.size());
}
// |expected_scan_results| contains the data expected to be provided to
// scan observers; if null, we default to a single BLE scan result.
void ProcessScanResultAndVerifyDevice(
const std::string& service_data,
multidevice::RemoteDeviceRef expected_remote_device,
bool is_background_advertisement,
const base::Optional<
std::vector<std::pair<ConnectionMedium, ConnectionRole>>>&
expected_scan_results = base::nullopt) {
std::vector<std::pair<ConnectionMedium, ConnectionRole>>
new_expected_results =
expected_scan_results.has_value()
? *expected_scan_results
: CreateSingleBleScanResult(is_background_advertisement);
const std::vector<FakeBleScannerObserver::Result>& results =
fake_delegate_->handled_scan_results();
fake_bluetooth_helper_->SetIdentifiedDevice(
service_data, expected_remote_device, is_background_advertisement);
size_t num_results_before_call = results.size();
std::unique_ptr<FakeBluetoothDevice> fake_bluetooth_device =
SimulateScanResult(service_data);
EXPECT_EQ(num_results_before_call + new_expected_results.size(),
results.size());
for (size_t i = 0; i < new_expected_results.size(); ++i) {
const auto& result =
results[results.size() - new_expected_results.size() + i];
EXPECT_EQ(expected_remote_device, result.remote_device);
EXPECT_EQ(fake_bluetooth_device.get(), result.bluetooth_device);
EXPECT_EQ(new_expected_results[i].first, result.connection_medium);
EXPECT_EQ(new_expected_results[i].second, result.connection_role);
}
}
void InvokeStartDiscoveryCallback(bool success, size_t command_index) {
if (!success) {
fake_ble_synchronizer_->TakeStartDiscoveryErrorCallback(command_index)
.Run();
return;
}
StartDiscoverySession();
fake_ble_synchronizer_->TakeStartDiscoveryCallback(command_index)
.Run(std::move(discovery_session_));
}
void InvokeStopDiscoveryCallback(bool success, size_t command_index) {
if (success) {
fake_ble_synchronizer_->GetStopDiscoveryCallback(command_index).Run();
} else {
fake_ble_synchronizer_->GetStopDiscoveryErrorCallback(command_index)
.Run();
}
}
size_t GetNumBleCommands() {
return fake_ble_synchronizer_->GetNumCommands();
}
bool discovery_session_is_active() {
return discovery_session_weak_ptr_.get();
}
FakeBluetoothHelper* fake_bluetooth_helper() {
return fake_bluetooth_helper_.get();
}
const multidevice::RemoteDeviceRefList& test_devices() {
return test_devices_;
}
private:
std::unique_ptr<FakeBluetoothDevice> SimulateScanResult(
const std::string& service_data) {
static const int16_t kFakeRssi = -70;
static const std::vector<uint8_t> kFakeEir;
// Scan result should not be received if there is no active discovery
// session.
EXPECT_TRUE(discovery_session_is_active());
auto fake_bluetooth_device = std::make_unique<FakeBluetoothDevice>(
service_data, mock_adapter_.get());
// Note: MockBluetoothAdapter provides no way to notify observers, so the
// observer callback must be invoked directly.
for (auto& observer : mock_adapter_->GetObservers()) {
observer.DeviceAdvertisementReceived(mock_adapter_.get(),
fake_bluetooth_device.get(),
kFakeRssi, kFakeEir);
}
return fake_bluetooth_device;
}
const multidevice::RemoteDeviceRefList test_devices_;
std::unique_ptr<FakeBleScannerObserver> fake_delegate_;
std::unique_ptr<FakeBluetoothHelper> fake_bluetooth_helper_;
std::unique_ptr<FakeBleSynchronizer> fake_ble_synchronizer_;
scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>> mock_adapter_;
std::unique_ptr<device::BluetoothDiscoverySession> discovery_session_;
FakeServiceDataProvider* fake_service_data_provider_ = nullptr;
base::WeakPtr<device::BluetoothDiscoverySession> discovery_session_weak_ptr_;
std::unique_ptr<BleScanner> ble_scanner_;
private:
DISALLOW_COPY_AND_ASSIGN(SecureChannelBleScannerImplTest);
};
TEST_F(SecureChannelBleScannerImplTest, UnrelatedScanResults) {
ConnectionAttemptDetails filter(DeviceIdPair(test_devices()[0].GetDeviceId(),
test_devices()[1].GetDeviceId()),
ConnectionMedium::kBluetoothLowEnergy,
ConnectionRole::kListenerRole);
AddScanRequest(filter);
InvokeStartDiscoveryCallback(true /* success */, 0u /* command_index */);
EXPECT_TRUE(discovery_session_is_active());
ProcessScanResultAndVerifyNoDeviceIdentified("unrelatedServiceData");
RemoveScanRequest(filter);
InvokeStopDiscoveryCallback(true /* success */, 1u /* command_index */);
EXPECT_FALSE(discovery_session_is_active());
}
TEST_F(SecureChannelBleScannerImplTest, IncorrectRole) {
ConnectionAttemptDetails filter(DeviceIdPair(test_devices()[0].GetDeviceId(),
test_devices()[1].GetDeviceId()),
ConnectionMedium::kBluetoothLowEnergy,
ConnectionRole::kListenerRole);
AddScanRequest(filter);
InvokeStartDiscoveryCallback(true /* success */, 0u /* command_index */);
EXPECT_TRUE(discovery_session_is_active());
// Set the device to be a foreground advertisement, even though the registered
// role is listener.
fake_bluetooth_helper()->SetIdentifiedDevice(
"wrongRoleServiceData", test_devices()[0],
false /* is_background_advertisement */);
ProcessScanResultAndVerifyNoDeviceIdentified("wrongRoleServiceData");
RemoveScanRequest(filter);
InvokeStopDiscoveryCallback(true /* success */, 1u /* command_index */);
EXPECT_FALSE(discovery_session_is_active());
}
TEST_F(SecureChannelBleScannerImplTest, IdentifyDevice_Background) {
ConnectionAttemptDetails filter(DeviceIdPair(test_devices()[0].GetDeviceId(),
test_devices()[1].GetDeviceId()),
ConnectionMedium::kBluetoothLowEnergy,
ConnectionRole::kListenerRole);
AddScanRequest(filter);
InvokeStartDiscoveryCallback(true /* success */, 0u /* command_index */);
EXPECT_TRUE(discovery_session_is_active());
ProcessScanResultAndVerifyDevice("device0ServiceData", test_devices()[0],
true /* is_background_advertisement */);
RemoveScanRequest(filter);
InvokeStopDiscoveryCallback(true /* success */, 1u /* command_index */);
EXPECT_FALSE(discovery_session_is_active());
}
TEST_F(SecureChannelBleScannerImplTest, IdentifyDevice_BleAndNearby) {
ConnectionAttemptDetails ble_filter(
DeviceIdPair(test_devices()[0].GetDeviceId(),
test_devices()[1].GetDeviceId()),
ConnectionMedium::kBluetoothLowEnergy, ConnectionRole::kListenerRole);
ConnectionAttemptDetails nearby_filter(
DeviceIdPair(test_devices()[0].GetDeviceId(),
test_devices()[1].GetDeviceId()),
ConnectionMedium::kNearbyConnections, ConnectionRole::kInitiatorRole);
AddScanRequest(ble_filter);
InvokeStartDiscoveryCallback(true /* success */, 0u /* command_index */);
EXPECT_TRUE(discovery_session_is_active());
AddScanRequest(nearby_filter);
EXPECT_TRUE(discovery_session_is_active());
std::vector<std::pair<ConnectionMedium, ConnectionRole>> expected_results{
{ConnectionMedium::kBluetoothLowEnergy, ConnectionRole::kListenerRole},
{ConnectionMedium::kNearbyConnections, ConnectionRole::kInitiatorRole}};
ProcessScanResultAndVerifyDevice("device0ServiceData", test_devices()[0],
true /* is_background_advertisement */,
expected_results);
RemoveScanRequest(ble_filter);
RemoveScanRequest(nearby_filter);
InvokeStopDiscoveryCallback(true /* success */, 1u /* command_index */);
EXPECT_FALSE(discovery_session_is_active());
}
TEST_F(SecureChannelBleScannerImplTest, IdentifyDevice_Foreground) {
ConnectionAttemptDetails filter(DeviceIdPair(test_devices()[0].GetDeviceId(),
test_devices()[1].GetDeviceId()),
ConnectionMedium::kBluetoothLowEnergy,
ConnectionRole::kInitiatorRole);
AddScanRequest(filter);
InvokeStartDiscoveryCallback(true /* success */, 0u /* command_index */);
EXPECT_TRUE(discovery_session_is_active());
ProcessScanResultAndVerifyDevice("device0ServiceData", test_devices()[0],
false /* is_background_advertisement */);
RemoveScanRequest(filter);
InvokeStopDiscoveryCallback(true /* success */, 1u /* command_index */);
EXPECT_FALSE(discovery_session_is_active());
}
TEST_F(SecureChannelBleScannerImplTest, IdentifyDevice_MultipleScans) {
ConnectionAttemptDetails filter_1(
DeviceIdPair(test_devices()[0].GetDeviceId(),
test_devices()[1].GetDeviceId()),
ConnectionMedium::kBluetoothLowEnergy, ConnectionRole::kInitiatorRole);
ConnectionAttemptDetails filter_2(
DeviceIdPair(test_devices()[2].GetDeviceId(),
test_devices()[1].GetDeviceId()),
ConnectionMedium::kBluetoothLowEnergy, ConnectionRole::kInitiatorRole);
AddScanRequest(filter_1);
AddScanRequest(filter_2);
InvokeStartDiscoveryCallback(true /* success */, 0u /* command_index */);
EXPECT_TRUE(discovery_session_is_active());
// Identify device 0.
ProcessScanResultAndVerifyDevice("device0ServiceData", test_devices()[0],
false /* is_background_advertisement */);
// Remove the identified device from the list of scan filters.
RemoveScanRequest(filter_1);
// No additional BLE command should have been posted, since the existing scan
// should not have been stopped.
EXPECT_EQ(1u, GetNumBleCommands());
EXPECT_TRUE(discovery_session_is_active());
// Remove the scan filter, and verify that the scan stopped.
RemoveScanRequest(filter_2);
InvokeStopDiscoveryCallback(true /* success */, 1u /* command_index */);
EXPECT_FALSE(discovery_session_is_active());
// Add the scan filter back again; this should start the discovery session
// back up again.
AddScanRequest(filter_2);
InvokeStartDiscoveryCallback(true /* success */, 2u /* command_index */);
EXPECT_TRUE(discovery_session_is_active());
// Identify device 2.
ProcessScanResultAndVerifyDevice("device2ServiceData", test_devices()[2],
false /* is_background_advertisement */);
// Remove the scan filter, and verify that the scan stopped.
RemoveScanRequest(filter_2);
InvokeStopDiscoveryCallback(true /* success */, 3u /* command_index */);
EXPECT_FALSE(discovery_session_is_active());
}
TEST_F(SecureChannelBleScannerImplTest, StartAndStopFailures) {
ConnectionAttemptDetails filter(DeviceIdPair(test_devices()[0].GetDeviceId(),
test_devices()[1].GetDeviceId()),
ConnectionMedium::kBluetoothLowEnergy,
ConnectionRole::kListenerRole);
AddScanRequest(filter);
// A request was made to start discovery; simulate this request failing.
InvokeStartDiscoveryCallback(false /* success */, 0u /* command_index */);
EXPECT_FALSE(discovery_session_is_active());
// BleScanner should have retried this attempt; simulate another failure.
InvokeStartDiscoveryCallback(false /* success */, 1u /* command_index */);
EXPECT_FALSE(discovery_session_is_active());
// Succeed this time.
InvokeStartDiscoveryCallback(true /* success */, 2u /* command_index */);
EXPECT_TRUE(discovery_session_is_active());
// Remove scan filters, which should trigger BleScanner to stop the
// discovery session.
RemoveScanRequest(filter);
// Simulate a failure to stop.
InvokeStopDiscoveryCallback(false /* success */, 3u /* command_index */);
EXPECT_TRUE(discovery_session_is_active());
// Simulate another failure.
InvokeStopDiscoveryCallback(false /* success */, 4u /* command_index */);
EXPECT_TRUE(discovery_session_is_active());
// Succeed this time.
InvokeStopDiscoveryCallback(true /* success */, 5u /* command_index */);
EXPECT_FALSE(discovery_session_is_active());
}
TEST_F(SecureChannelBleScannerImplTest, StartAndStop_EdgeCases) {
ConnectionAttemptDetails filter(DeviceIdPair(test_devices()[0].GetDeviceId(),
test_devices()[1].GetDeviceId()),
ConnectionMedium::kBluetoothLowEnergy,
ConnectionRole::kListenerRole);
AddScanRequest(filter);
// Remove scan filters before the start discovery callback succeeds.
RemoveScanRequest(filter);
// Complete starting the discovery session.
InvokeStartDiscoveryCallback(true /* success */, 0u /* command_index */);
EXPECT_TRUE(discovery_session_is_active());
// BleScanner should have realized that it should now stop the discovery
// session. Invoke the pending stop discovery callback.
InvokeStopDiscoveryCallback(true /* success */, 1u /* command_index */);
EXPECT_FALSE(discovery_session_is_active());
}
TEST_F(SecureChannelBleScannerImplTest, StartAndStopFailures_EdgeCases) {
ConnectionAttemptDetails filter(DeviceIdPair(test_devices()[0].GetDeviceId(),
test_devices()[1].GetDeviceId()),
ConnectionMedium::kBluetoothLowEnergy,
ConnectionRole::kListenerRole);
AddScanRequest(filter);
// Remove scan filters before the start discovery callback succeeds.
RemoveScanRequest(filter);
// Fail the pending call to start a discovery session.
InvokeStartDiscoveryCallback(false /* success */, 0u /* command_index */);
EXPECT_FALSE(discovery_session_is_active());
// No additional BLE command should have been posted.
EXPECT_EQ(1u, GetNumBleCommands());
}
} // namespace secure_channel
} // namespace chromeos