123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514 |
- // Copyright 2013 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 "device/bluetooth/bluetooth_adapter_mac.h"
- #include "base/memory/raw_ptr.h"
- #import <Foundation/Foundation.h>
- #include <memory>
- #include "base/bind.h"
- #include "base/files/file_path.h"
- #include "base/files/file_util.h"
- #include "base/files/scoped_temp_dir.h"
- #include "base/memory/ptr_util.h"
- #include "base/memory/ref_counted.h"
- #include "base/task/sequenced_task_runner.h"
- #include "base/test/bind.h"
- #include "base/test/task_environment.h"
- #include "base/test/test_simple_task_runner.h"
- #include "build/build_config.h"
- #include "device/bluetooth/bluetooth_adapter.h"
- #include "device/bluetooth/bluetooth_common.h"
- #include "device/bluetooth/bluetooth_discovery_session.h"
- #include "device/bluetooth/bluetooth_discovery_session_outcome.h"
- #import "device/bluetooth/bluetooth_low_energy_device_mac.h"
- #include "device/bluetooth/bluetooth_low_energy_device_watcher_mac.h"
- #import "device/bluetooth/test/mock_bluetooth_cbperipheral_mac.h"
- #import "device/bluetooth/test/mock_bluetooth_central_manager_mac.h"
- #import "device/bluetooth/test/test_bluetooth_adapter_observer.h"
- #include "testing/gtest/include/gtest/gtest.h"
- #if BUILDFLAG(IS_IOS)
- #import <CoreBluetooth/CoreBluetooth.h>
- #else // !BUILDFLAG(IS_IOS)
- #import <IOBluetooth/IOBluetooth.h>
- #endif // BUILDFLAG(IS_IOS)
- namespace {
- const char kTestPropertyListFileName[] = "test_property_list_file.plist";
- // |kTestHashAddress| is the hash corresponding to identifier |kTestNSUUID|.
- const char kTestNSUUID[] = "00000000-1111-2222-3333-444444444444";
- const char kTestHashAddress[] = "D1:6F:E3:22:FD:5B";
- const int kTestRssi = 0;
- NSDictionary* CreateTestPropertyListData() {
- return @{
- @"CoreBluetoothCache" : @{
- @"00000000-1111-2222-3333-444444444444" : @{
- @"DeviceAddress" : @"22-22-22-22-22-22",
- @"DeviceAddressType" : @1,
- @"ServiceChangedHandle" : @3,
- @"ServiceChangeSubscribed" : @0,
- @"ServiceDiscoveryComplete" : @0
- }
- }
- };
- }
- bool IsTestDeviceSystemPaired(const std::string& address) {
- return true;
- }
- } // namespace
- namespace device {
- class BluetoothAdapterMacTest : public testing::Test {
- public:
- class FakeBluetoothLowEnergyDeviceWatcherMac
- : public BluetoothLowEnergyDeviceWatcherMac {
- public:
- FakeBluetoothLowEnergyDeviceWatcherMac(
- scoped_refptr<base::SequencedTaskRunner> ui_thread_task_runner,
- LowEnergyDeviceListUpdatedCallback callback)
- : BluetoothLowEnergyDeviceWatcherMac(ui_thread_task_runner, callback),
- weak_ptr_factory_(this) {}
- void SimulatePropertyListFileChanged(
- const base::FilePath& path,
- const std::string& changed_file_content) {
- auto expected_file_size = changed_file_content.length();
- ASSERT_EQ(static_cast<int>(expected_file_size),
- base::WriteFile(path, changed_file_content.data(),
- expected_file_size));
- OnPropertyListFileChangedOnFileThread(path, false /* error */);
- }
- private:
- ~FakeBluetoothLowEnergyDeviceWatcherMac() override = default;
- void Init() override { ReadBluetoothPropertyListFile(); }
- void ReadBluetoothPropertyListFile() override {
- low_energy_device_list_updated_callback().Run(
- ParseBluetoothDevicePropertyListData(CreateTestPropertyListData()));
- }
- base::WeakPtrFactory<FakeBluetoothLowEnergyDeviceWatcherMac>
- weak_ptr_factory_;
- };
- BluetoothAdapterMacTest()
- : ui_task_runner_(new base::TestSimpleTaskRunner()),
- adapter_(new BluetoothAdapterMac()),
- adapter_mac_(static_cast<BluetoothAdapterMac*>(adapter_.get())),
- observer_(adapter_),
- callback_count_(0),
- error_callback_count_(0) {
- adapter_mac_->SetGetDevicePairedStatusCallbackForTesting(
- base::BindRepeating(&IsTestDeviceSystemPaired));
- adapter_mac_->InitForTest(ui_task_runner_);
- fake_low_energy_device_watcher_ =
- base::MakeRefCounted<FakeBluetoothLowEnergyDeviceWatcherMac>(
- ui_task_runner_,
- base::BindRepeating(
- &BluetoothAdapterMac::UpdateKnownLowEnergyDevices,
- adapter_mac_->weak_ptr_factory_.GetWeakPtr()));
- }
- void SetUp() override {
- ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
- test_property_list_file_path_ =
- temp_dir_.GetPath().AppendASCII(kTestPropertyListFileName);
- }
- void TearDown() override { task_environment_.RunUntilIdle(); }
- // Helper methods for setup and access to BluetoothAdapterMacTest's members.
- void PollAdapter() { adapter_mac_->PollAdapter(); }
- void SetHostControllerPowerFunction(bool powered) {
- adapter_mac_->SetHostControllerStateFunctionForTesting(
- base::BindLambdaForTesting([powered] {
- BluetoothAdapterMac::HostControllerState state;
- state.classic_powered = powered;
- return state;
- }));
- }
- void LowEnergyDeviceUpdated(CBPeripheral* peripheral,
- NSDictionary* advertisement_data,
- int rssi) {
- adapter_mac_->LowEnergyDeviceUpdated(peripheral, advertisement_data, rssi);
- }
- BluetoothDevice* GetDevice(const std::string& address) {
- return adapter_->GetDevice(address);
- }
- CBPeripheral* CreateMockPeripheral(const char* identifier) {
- base::scoped_nsobject<MockCBPeripheral> mock_peripheral(
- [[MockCBPeripheral alloc] initWithUTF8StringIdentifier:identifier]);
- return [[mock_peripheral peripheral] retain];
- }
- NSDictionary* AdvertisementData() {
- NSDictionary* advertisement_data = @{
- CBAdvertisementDataIsConnectable : @(YES),
- CBAdvertisementDataServiceDataKey : @{},
- };
- return [advertisement_data retain];
- }
- std::string GetHashAddress(CBPeripheral* peripheral) {
- return BluetoothLowEnergyDeviceMac::GetPeripheralHashAddress(peripheral);
- }
- int NumDevices() { return adapter_mac_->devices_.size(); }
- bool DevicePresent(CBPeripheral* peripheral) {
- BluetoothDevice* device = adapter_mac_->GetDevice(
- BluetoothLowEnergyDeviceMac::GetPeripheralHashAddress(peripheral));
- return (device != NULL);
- }
- bool SetMockCentralManager(CBManagerState desired_state) {
- mock_central_manager_.reset([[MockCentralManager alloc] init]);
- [mock_central_manager_ setState:desired_state];
- CBCentralManager* centralManager =
- static_cast<CBCentralManager*>(mock_central_manager_.get());
- adapter_mac_->SetCentralManagerForTesting(centralManager);
- return true;
- }
- void OnStartDiscoverySessionSuccess(
- std::unique_ptr<device::BluetoothDiscoverySession> discovery_session) {
- active_sessions_.push_back(std::move(discovery_session));
- Callback();
- }
- int NumDiscoverySessions() { return adapter_mac_->NumDiscoverySessions(); }
- void SetFakeLowEnergyDeviceWatcher() {
- adapter_mac_->SetLowEnergyDeviceWatcherForTesting(
- fake_low_energy_device_watcher_);
- }
- // Generic callbacks.
- void Callback() { ++callback_count_; }
- void ErrorCallback() { ++error_callback_count_; }
- void DiscoveryErrorCallback(UMABluetoothDiscoverySessionOutcome) {
- ++error_callback_count_;
- }
- protected:
- base::test::TaskEnvironment task_environment_;
- scoped_refptr<base::TestSimpleTaskRunner> ui_task_runner_;
- scoped_refptr<BluetoothAdapter> adapter_;
- raw_ptr<BluetoothAdapterMac> adapter_mac_;
- scoped_refptr<FakeBluetoothLowEnergyDeviceWatcherMac>
- fake_low_energy_device_watcher_;
- TestBluetoothAdapterObserver observer_;
- std::vector<std::unique_ptr<BluetoothDiscoverySession>> active_sessions_;
- // Owned by |adapter_mac_|.
- base::scoped_nsobject<MockCentralManager> mock_central_manager_;
- int callback_count_;
- int error_callback_count_;
- base::ScopedTempDir temp_dir_;
- base::FilePath test_property_list_file_path_;
- };
- TEST_F(BluetoothAdapterMacTest, Poll) {
- PollAdapter();
- EXPECT_TRUE(ui_task_runner_->HasPendingTask());
- }
- TEST_F(BluetoothAdapterMacTest, PollAndChangePower) {
- // By default the adapter is powered off, check that this expectation matches
- // reality.
- EXPECT_FALSE(adapter_mac_->IsPowered());
- EXPECT_EQ(0, observer_.powered_changed_count());
- SetHostControllerPowerFunction(true);
- PollAdapter();
- EXPECT_TRUE(ui_task_runner_->HasPendingTask());
- ui_task_runner_->RunPendingTasks();
- EXPECT_EQ(1, observer_.powered_changed_count());
- EXPECT_TRUE(observer_.last_powered());
- EXPECT_TRUE(adapter_mac_->IsPowered());
- SetHostControllerPowerFunction(false);
- PollAdapter();
- EXPECT_TRUE(ui_task_runner_->HasPendingTask());
- ui_task_runner_->RunPendingTasks();
- EXPECT_EQ(2, observer_.powered_changed_count());
- EXPECT_FALSE(observer_.last_powered());
- EXPECT_FALSE(adapter_mac_->IsPowered());
- }
- TEST_F(BluetoothAdapterMacTest, AddDiscoverySessionWithLowEnergyFilter) {
- if (!SetMockCentralManager(CBManagerStatePoweredOn))
- return;
- EXPECT_EQ(0, [mock_central_manager_ scanForPeripheralsCallCount]);
- EXPECT_EQ(0, NumDiscoverySessions());
- std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter(
- new BluetoothDiscoveryFilter(BLUETOOTH_TRANSPORT_LE));
- adapter_mac_->StartDiscoverySessionWithFilter(
- std::move(discovery_filter),
- /*client_name=*/std::string(),
- base::BindOnce(&BluetoothAdapterMacTest::OnStartDiscoverySessionSuccess,
- base::Unretained(this)),
- base::BindOnce(&BluetoothAdapterMacTest::ErrorCallback,
- base::Unretained(this)));
- EXPECT_TRUE(ui_task_runner_->HasPendingTask());
- ui_task_runner_->RunPendingTasks();
- EXPECT_EQ(1, callback_count_);
- EXPECT_EQ(0, error_callback_count_);
- EXPECT_EQ(1, NumDiscoverySessions());
- // Check that adding a discovery session resulted in
- // scanForPeripheralsWithServices being called on the Central Manager.
- EXPECT_EQ(1, [mock_central_manager_ scanForPeripheralsCallCount]);
- }
- // TODO(krstnmnlsn): Test changing the filter when adding the second discovery
- // session (once we have that ability).
- TEST_F(BluetoothAdapterMacTest, AddSecondDiscoverySessionWithLowEnergyFilter) {
- if (!SetMockCentralManager(CBManagerStatePoweredOn))
- return;
- std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter(
- new BluetoothDiscoveryFilter(BLUETOOTH_TRANSPORT_LE));
- std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter2(
- new BluetoothDiscoveryFilter(BLUETOOTH_TRANSPORT_LE));
- // Adding uuid to first discovery session so that there is a change to be made
- // when starting the second session.
- BluetoothDiscoveryFilter::DeviceInfoFilter device_filter;
- device_filter.uuids.insert(device::BluetoothUUID("1000"));
- discovery_filter->AddDeviceFilter(device_filter);
- adapter_mac_->StartDiscoverySessionWithFilter(
- std::move(discovery_filter),
- /*client_name=*/std::string(),
- base::BindOnce(&BluetoothAdapterMacTest::OnStartDiscoverySessionSuccess,
- base::Unretained(this)),
- base::BindOnce(&BluetoothAdapterMacTest::ErrorCallback,
- base::Unretained(this)));
- EXPECT_TRUE(ui_task_runner_->HasPendingTask());
- ui_task_runner_->RunPendingTasks();
- EXPECT_EQ(1, callback_count_);
- EXPECT_EQ(0, error_callback_count_);
- EXPECT_EQ(1, NumDiscoverySessions());
- // We replaced the success callback handed to AddDiscoverySession, so
- // |adapter_mac_| should remain in a discovering state indefinitely.
- EXPECT_TRUE(adapter_mac_->IsDiscovering());
- adapter_mac_->StartDiscoverySessionWithFilter(
- std::move(discovery_filter2),
- /*client_name=*/std::string(),
- base::BindOnce(&BluetoothAdapterMacTest::OnStartDiscoverySessionSuccess,
- base::Unretained(this)),
- base::BindOnce(&BluetoothAdapterMacTest::ErrorCallback,
- base::Unretained(this)));
- EXPECT_TRUE(ui_task_runner_->HasPendingTask());
- ui_task_runner_->RunPendingTasks();
- EXPECT_EQ(2, [mock_central_manager_ scanForPeripheralsCallCount]);
- EXPECT_EQ(2, callback_count_);
- EXPECT_EQ(0, error_callback_count_);
- EXPECT_EQ(2, NumDiscoverySessions());
- }
- TEST_F(BluetoothAdapterMacTest, RemoveDiscoverySessionWithLowEnergyFilter) {
- if (!SetMockCentralManager(CBManagerStatePoweredOn))
- return;
- EXPECT_EQ(0, [mock_central_manager_ scanForPeripheralsCallCount]);
- std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter(
- new BluetoothDiscoveryFilter(BLUETOOTH_TRANSPORT_LE));
- adapter_mac_->StartDiscoverySessionWithFilter(
- std::move(discovery_filter),
- /*client_name=*/std::string(),
- base::BindOnce(&BluetoothAdapterMacTest::OnStartDiscoverySessionSuccess,
- base::Unretained(this)),
- base::BindOnce(&BluetoothAdapterMacTest::ErrorCallback,
- base::Unretained(this)));
- EXPECT_TRUE(ui_task_runner_->HasPendingTask());
- ui_task_runner_->RunPendingTasks();
- EXPECT_EQ(1, callback_count_);
- EXPECT_EQ(0, error_callback_count_);
- EXPECT_EQ(1, NumDiscoverySessions());
- EXPECT_EQ(0, [mock_central_manager_ stopScanCallCount]);
- active_sessions_[0]->Stop(
- base::BindOnce(&BluetoothAdapterMacTest::Callback,
- base::Unretained(this)),
- base::BindOnce(&BluetoothAdapterMacTest::ErrorCallback,
- base::Unretained(this)));
- EXPECT_EQ(2, callback_count_);
- EXPECT_EQ(0, error_callback_count_);
- EXPECT_EQ(0, NumDiscoverySessions());
- // Check that removing the discovery session resulted in stopScan being called
- // on the Central Manager.
- EXPECT_EQ(1, [mock_central_manager_ stopScanCallCount]);
- }
- TEST_F(BluetoothAdapterMacTest, CheckGetPeripheralHashAddress) {
- if (!SetMockCentralManager(CBManagerStatePoweredOn))
- return;
- base::scoped_nsobject<CBPeripheral> mock_peripheral(
- CreateMockPeripheral(kTestNSUUID));
- if (!mock_peripheral)
- return;
- EXPECT_EQ(kTestHashAddress, GetHashAddress(mock_peripheral));
- }
- TEST_F(BluetoothAdapterMacTest, LowEnergyDeviceUpdatedNewDevice) {
- if (!SetMockCentralManager(CBManagerStatePoweredOn))
- return;
- base::scoped_nsobject<CBPeripheral> mock_peripheral(
- CreateMockPeripheral(kTestNSUUID));
- if (!mock_peripheral)
- return;
- base::scoped_nsobject<NSDictionary> advertisement_data(AdvertisementData());
- EXPECT_EQ(0, NumDevices());
- EXPECT_FALSE(DevicePresent(mock_peripheral));
- LowEnergyDeviceUpdated(mock_peripheral, advertisement_data, kTestRssi);
- EXPECT_EQ(1, NumDevices());
- EXPECT_TRUE(DevicePresent(mock_peripheral));
- }
- TEST_F(BluetoothAdapterMacTest, GetSystemPairedLowEnergyDevice) {
- SetFakeLowEnergyDeviceWatcher();
- ui_task_runner_->RunUntilIdle();
- EXPECT_TRUE(
- adapter_mac_->IsBluetoothLowEnergyDeviceSystemPaired(kTestNSUUID));
- }
- TEST_F(BluetoothAdapterMacTest, GetNewlyPairedLowEnergyDevice) {
- constexpr char kPropertyListFileContentWithAddedDevice[] =
- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
- "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" "
- "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"
- "<plist version=\"1.0\">"
- "<dict>"
- " <key>CoreBluetoothCache</key>"
- " <dict> "
- " <key>E7F8589A-A7D9-4B94-9A08-D89076A159F4</key>"
- " <dict> "
- " <key>DeviceAddress</key>"
- " <string>11-11-11-11-11-11</string>"
- " <key>DeviceAddressType</key>"
- " <integer>1</integer>"
- " <key>ServiceChangedHandle</key>"
- " <integer>3</integer>"
- " <key>ServiceChangeSubscribed</key>"
- " <integer>0</integer>"
- " <key>ServiceDiscoveryComplete</key>"
- " <integer>0</integer>"
- " </dict>"
- " <key>00000000-1111-2222-3333-444444444444</key>"
- " <dict> "
- " <key>DeviceAddress</key>"
- " <string>22-22-22-22-22-22</string>"
- " <key>DeviceAddressType</key>"
- " <integer>1</integer>"
- " <key>ServiceChangedHandle</key>"
- " <integer>3</integer>"
- " <key>ServiceChangeSubscribed</key>"
- " <integer>0</integer>"
- " <key>ServiceDiscoveryComplete</key>"
- " <integer>0</integer>"
- " </dict>"
- " </dict>"
- "</dict>"
- "</plist>";
- const char kTestAddedDeviceNSUUID[] = "E7F8589A-A7D9-4B94-9A08-D89076A159F4";
- ASSERT_TRUE(SetMockCentralManager(CBManagerStatePoweredOn));
- base::scoped_nsobject<CBPeripheral> mock_peripheral_one(
- CreateMockPeripheral(kTestNSUUID));
- ASSERT_TRUE(mock_peripheral_one);
- LowEnergyDeviceUpdated(
- mock_peripheral_one,
- base::scoped_nsobject<NSDictionary>(AdvertisementData()), kTestRssi);
- base::scoped_nsobject<CBPeripheral> mock_peripheral_two(
- CreateMockPeripheral(kTestAddedDeviceNSUUID));
- ASSERT_TRUE(mock_peripheral_two);
- LowEnergyDeviceUpdated(
- mock_peripheral_two,
- base::scoped_nsobject<NSDictionary>(AdvertisementData()), kTestRssi);
- observer_.Reset();
- // BluetoothAdapterMac only notifies observers of changed devices detected by
- // BluetoothLowEnergyDeviceWatcherMac if the device has been already known to
- // the system(i.e. the changed device is in BluetoothAdatper::devices_). As
- // so, add mock devices prior to setting BluetoothLowenergyDeviceWatcherMac.
- SetFakeLowEnergyDeviceWatcher();
- EXPECT_EQ(1, observer_.device_changed_count());
- observer_.Reset();
- fake_low_energy_device_watcher_->SimulatePropertyListFileChanged(
- test_property_list_file_path_, kPropertyListFileContentWithAddedDevice);
- ui_task_runner_->RunUntilIdle();
- EXPECT_EQ(1, observer_.device_changed_count());
- EXPECT_TRUE(adapter_mac_->IsBluetoothLowEnergyDeviceSystemPaired(
- kTestAddedDeviceNSUUID));
- }
- TEST_F(BluetoothAdapterMacTest, NotifyObserverWhenDeviceIsUnpaired) {
- constexpr char kPropertyListFileContentWithRemovedDevice[] =
- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
- "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" "
- "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"
- "<plist version=\"1.0\">"
- "<dict>"
- " <key>CoreBluetoothCache</key>"
- " <dict> "
- " </dict>"
- "</dict>"
- "</plist>";
- if (!SetMockCentralManager(CBManagerStatePoweredOn))
- return;
- base::scoped_nsobject<CBPeripheral> mock_peripheral(
- CreateMockPeripheral(kTestNSUUID));
- if (!mock_peripheral)
- return;
- LowEnergyDeviceUpdated(
- mock_peripheral, base::scoped_nsobject<NSDictionary>(AdvertisementData()),
- kTestRssi);
- observer_.Reset();
- SetFakeLowEnergyDeviceWatcher();
- EXPECT_EQ(1, observer_.device_changed_count());
- observer_.Reset();
- fake_low_energy_device_watcher_->SimulatePropertyListFileChanged(
- test_property_list_file_path_, kPropertyListFileContentWithRemovedDevice);
- ui_task_runner_->RunUntilIdle();
- EXPECT_EQ(1, observer_.device_changed_count());
- EXPECT_FALSE(
- adapter_mac_->IsBluetoothLowEnergyDeviceSystemPaired(kTestNSUUID));
- }
- } // namespace device
|