| // 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 "chromecast/device/bluetooth/le/gatt_client_manager_impl.h" |
| |
| #include "base/bind.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/message_loop/message_pump_type.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/task_environment.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "chromecast/device/bluetooth/bluetooth_util.h" |
| #include "chromecast/device/bluetooth/le/remote_characteristic_impl.h" |
| #include "chromecast/device/bluetooth/le/remote_descriptor_impl.h" |
| #include "chromecast/device/bluetooth/le/remote_device_impl.h" |
| #include "chromecast/device/bluetooth/le/remote_service.h" |
| #include "chromecast/device/bluetooth/shlib/mock_gatt_client.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::_; |
| using ::testing::Return; |
| |
| namespace chromecast { |
| namespace bluetooth { |
| |
| namespace { |
| |
| const bluetooth_v2_shlib::Addr kTestAddr1 = { |
| {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}}; |
| const bluetooth_v2_shlib::Addr kTestAddr2 = { |
| {0x10, 0x11, 0x12, 0x13, 0x14, 0x15}}; |
| const bluetooth_v2_shlib::Addr kTestAddr3 = { |
| {0x20, 0x21, 0x22, 0x23, 0x24, 0x25}}; |
| const bluetooth_v2_shlib::Addr kTestAddr4 = { |
| {0x30, 0x31, 0x32, 0x33, 0x34, 0x35}}; |
| const bluetooth_v2_shlib::Addr kTestAddr5 = { |
| {0x40, 0x41, 0x42, 0x43, 0x44, 0x45}}; |
| |
| class MockGattClientManagerObserver : public GattClientManager::Observer { |
| public: |
| MOCK_METHOD2(OnConnectChanged, |
| void(scoped_refptr<RemoteDevice> device, bool connected)); |
| MOCK_METHOD2(OnMtuChanged, void(scoped_refptr<RemoteDevice> device, int mtu)); |
| MOCK_METHOD2(OnServicesUpdated, |
| void(scoped_refptr<RemoteDevice> device, |
| std::vector<scoped_refptr<RemoteService>> services)); |
| MOCK_METHOD3(OnCharacteristicNotification, |
| void(scoped_refptr<RemoteDevice> device, |
| scoped_refptr<RemoteCharacteristic> characteristic, |
| std::vector<uint8_t> value)); |
| }; |
| |
| std::vector<bluetooth_v2_shlib::Gatt::Service> GenerateServices() { |
| std::vector<bluetooth_v2_shlib::Gatt::Service> ret; |
| |
| bluetooth_v2_shlib::Gatt::Service service; |
| bluetooth_v2_shlib::Gatt::Characteristic characteristic; |
| bluetooth_v2_shlib::Gatt::Descriptor descriptor; |
| |
| service.uuid = {{0x1}}; |
| service.handle = 0x1; |
| service.primary = true; |
| |
| // Generate a characteristic that supports notification only. |
| characteristic.uuid = {{0x1, 0x1}}; |
| characteristic.handle = 0x2; |
| characteristic.permissions = |
| static_cast<bluetooth_v2_shlib::Gatt::Permissions>( |
| bluetooth_v2_shlib::Gatt::PERMISSION_READ | |
| bluetooth_v2_shlib::Gatt::PERMISSION_WRITE); |
| characteristic.properties = bluetooth_v2_shlib::Gatt::PROPERTY_NOTIFY; |
| |
| descriptor.uuid = {{0x1, 0x1, 0x1}}; |
| descriptor.handle = 0x3; |
| descriptor.permissions = static_cast<bluetooth_v2_shlib::Gatt::Permissions>( |
| bluetooth_v2_shlib::Gatt::PERMISSION_READ | |
| bluetooth_v2_shlib::Gatt::PERMISSION_WRITE); |
| characteristic.descriptors.push_back(descriptor); |
| |
| descriptor.uuid = RemoteDescriptor::kCccdUuid; |
| descriptor.handle = 0x4; |
| descriptor.permissions = static_cast<bluetooth_v2_shlib::Gatt::Permissions>( |
| bluetooth_v2_shlib::Gatt::PERMISSION_READ | |
| bluetooth_v2_shlib::Gatt::PERMISSION_WRITE); |
| characteristic.descriptors.push_back(descriptor); |
| service.characteristics.push_back(characteristic); |
| |
| // Generate a characteristic that does not support notification or indication. |
| characteristic.uuid = {{0x1, 0x2}}; |
| characteristic.handle = 0x5; |
| characteristic.permissions = |
| static_cast<bluetooth_v2_shlib::Gatt::Permissions>( |
| bluetooth_v2_shlib::Gatt::PERMISSION_READ | |
| bluetooth_v2_shlib::Gatt::PERMISSION_WRITE); |
| characteristic.properties = |
| static_cast<bluetooth_v2_shlib::Gatt::Properties>(0); |
| characteristic.descriptors.clear(); |
| service.characteristics.push_back(characteristic); |
| |
| // Generate a characteristic that supports indication only. |
| characteristic.uuid = {{0x1, 0x3}}; |
| characteristic.handle = 0x6; |
| characteristic.permissions = |
| static_cast<bluetooth_v2_shlib::Gatt::Permissions>( |
| bluetooth_v2_shlib::Gatt::PERMISSION_READ | |
| bluetooth_v2_shlib::Gatt::PERMISSION_WRITE); |
| characteristic.properties = bluetooth_v2_shlib::Gatt::PROPERTY_INDICATE; |
| |
| descriptor.uuid = {{0x1, 0x3, 0x1}}; |
| descriptor.handle = 0x7; |
| descriptor.permissions = static_cast<bluetooth_v2_shlib::Gatt::Permissions>( |
| bluetooth_v2_shlib::Gatt::PERMISSION_READ | |
| bluetooth_v2_shlib::Gatt::PERMISSION_WRITE); |
| characteristic.descriptors.push_back(descriptor); |
| |
| descriptor.uuid = RemoteDescriptor::kCccdUuid; |
| descriptor.handle = 0x8; |
| descriptor.permissions = static_cast<bluetooth_v2_shlib::Gatt::Permissions>( |
| bluetooth_v2_shlib::Gatt::PERMISSION_READ | |
| bluetooth_v2_shlib::Gatt::PERMISSION_WRITE); |
| characteristic.descriptors.push_back(descriptor); |
| service.characteristics.push_back(characteristic); |
| |
| // Generate a characteristic that supports both notification and indication. |
| characteristic.uuid = {{0x1, 0x4}}; |
| characteristic.handle = 0x9; |
| characteristic.permissions = |
| static_cast<bluetooth_v2_shlib::Gatt::Permissions>( |
| bluetooth_v2_shlib::Gatt::PERMISSION_READ | |
| bluetooth_v2_shlib::Gatt::PERMISSION_WRITE); |
| characteristic.properties = static_cast<bluetooth_v2_shlib::Gatt::Properties>( |
| bluetooth_v2_shlib::Gatt::PROPERTY_NOTIFY | |
| bluetooth_v2_shlib::Gatt::PROPERTY_INDICATE); |
| characteristic.descriptors.clear(); |
| |
| descriptor.uuid = {{0x1, 0x4, 0x1}}; |
| descriptor.handle = 0xA; |
| descriptor.permissions = static_cast<bluetooth_v2_shlib::Gatt::Permissions>( |
| bluetooth_v2_shlib::Gatt::PERMISSION_READ | |
| bluetooth_v2_shlib::Gatt::PERMISSION_WRITE); |
| characteristic.descriptors.push_back(descriptor); |
| |
| descriptor.uuid = RemoteDescriptor::kCccdUuid; |
| descriptor.handle = 0xB; |
| descriptor.permissions = static_cast<bluetooth_v2_shlib::Gatt::Permissions>( |
| bluetooth_v2_shlib::Gatt::PERMISSION_READ | |
| bluetooth_v2_shlib::Gatt::PERMISSION_WRITE); |
| characteristic.descriptors.push_back(descriptor); |
| service.characteristics.push_back(characteristic); |
| |
| ret.push_back(service); |
| |
| service.uuid = {{0x2}}; |
| service.handle = 0xC; |
| service.primary = true; |
| service.characteristics.clear(); |
| ret.push_back(service); |
| |
| return ret; |
| } |
| |
| class GattClientManagerTest : public ::testing::Test { |
| public: |
| void SetUp() override { |
| gatt_client_ = std::make_unique<bluetooth_v2_shlib::MockGattClient>(); |
| gatt_client_manager_ = |
| std::make_unique<GattClientManagerImpl>(gatt_client_.get()); |
| observer_ = std::make_unique<MockGattClientManagerObserver>(); |
| |
| // Normally bluetooth_manager does this. |
| gatt_client_->SetDelegate(gatt_client_manager_.get()); |
| gatt_client_manager_->Initialize(base::ThreadTaskRunnerHandle::Get()); |
| gatt_client_manager_->AddObserver(observer_.get()); |
| } |
| |
| void TearDown() override { |
| gatt_client_->SetDelegate(nullptr); |
| gatt_client_manager_->RemoveObserver(observer_.get()); |
| gatt_client_manager_->Finalize(); |
| } |
| |
| scoped_refptr<RemoteDevice> GetDevice(const bluetooth_v2_shlib::Addr& addr) { |
| scoped_refptr<RemoteDevice> ret; |
| gatt_client_manager_->GetDevice( |
| addr, base::BindOnce( |
| [](scoped_refptr<RemoteDevice>* ret_ptr, |
| scoped_refptr<RemoteDevice> result) { *ret_ptr = result; }, |
| &ret)); |
| |
| return ret; |
| } |
| |
| std::vector<scoped_refptr<RemoteService>> GetServices(RemoteDevice* device) { |
| std::vector<scoped_refptr<RemoteService>> ret; |
| device->GetServices(base::BindOnce( |
| [](std::vector<scoped_refptr<RemoteService>>* ret_ptr, |
| std::vector<scoped_refptr<RemoteService>> result) { |
| *ret_ptr = result; |
| }, |
| &ret)); |
| |
| return ret; |
| } |
| |
| scoped_refptr<RemoteService> GetServiceByUuid( |
| RemoteDevice* device, |
| const bluetooth_v2_shlib::Uuid& uuid) { |
| scoped_refptr<RemoteService> ret; |
| device->GetServiceByUuid(uuid, base::BindOnce( |
| [](scoped_refptr<RemoteService>* ret_ptr, |
| scoped_refptr<RemoteService> result) { |
| *ret_ptr = result; |
| }, |
| &ret)); |
| return ret; |
| } |
| |
| void Connect(const bluetooth_v2_shlib::Addr& addr) { |
| EXPECT_CALL(*gatt_client_, Connect(addr)).WillOnce(Return(true)); |
| scoped_refptr<RemoteDevice> device = GetDevice(addr); |
| EXPECT_CALL(cb_, Run(true)); |
| device->Connect(cb_.Get()); |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| EXPECT_CALL(*gatt_client_, GetServices(addr)).WillOnce(Return(true)); |
| delegate->OnConnectChanged(addr, true /* status */, true /* connected */); |
| delegate->OnGetServices(addr, {}); |
| ASSERT_TRUE(device->IsConnected()); |
| } |
| |
| base::MockCallback<RemoteDevice::StatusCallback> cb_; |
| base::test::SingleThreadTaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| std::unique_ptr<GattClientManagerImpl> gatt_client_manager_; |
| std::unique_ptr<bluetooth_v2_shlib::MockGattClient> gatt_client_; |
| std::unique_ptr<MockGattClientManagerObserver> observer_; |
| }; |
| |
| } // namespace |
| |
| TEST_F(GattClientManagerTest, RemoteDeviceConnect) { |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| |
| scoped_refptr<RemoteDevice> device = GetDevice(kTestAddr1); |
| EXPECT_FALSE(device->IsConnected()); |
| EXPECT_FALSE(gatt_client_manager_->IsConnectedLeDevice(kTestAddr1)); |
| EXPECT_EQ(kTestAddr1, device->addr()); |
| |
| // Disconnect from an already disconnected device. |
| EXPECT_CALL(*gatt_client_, Disconnect(kTestAddr1)).WillOnce(Return(false)); |
| EXPECT_CALL(*gatt_client_, ClearPendingDisconnect(kTestAddr1)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(cb_, Run(true)); |
| device->Disconnect(cb_.Get()); |
| |
| // These should fail if we're not connected. |
| EXPECT_CALL(cb_, Run(false)); |
| device->CreateBond(cb_.Get()); |
| |
| base::MockCallback<RemoteDevice::RssiCallback> rssi_cb; |
| EXPECT_CALL(rssi_cb, Run(false, _)); |
| device->ReadRemoteRssi(rssi_cb.Get()); |
| |
| EXPECT_CALL(cb_, Run(false)); |
| device->RequestMtu(512, cb_.Get()); |
| |
| EXPECT_CALL(cb_, Run(false)); |
| device->ConnectionParameterUpdate(10, 10, 50, 100, cb_.Get()); |
| |
| // First connect request fails right away. |
| EXPECT_CALL(*gatt_client_, Connect(kTestAddr1)).WillOnce(Return(false)); |
| EXPECT_CALL(*gatt_client_, ClearPendingConnect(kTestAddr1)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(cb_, Run(false)); |
| device->Connect(cb_.Get()); |
| EXPECT_FALSE(device->IsConnected()); |
| |
| // Second connect request succeeds. |
| EXPECT_CALL(*gatt_client_, Connect(kTestAddr1)).WillOnce(Return(true)); |
| EXPECT_CALL(cb_, Run(true)); |
| device->Connect(cb_.Get()); |
| EXPECT_CALL(*gatt_client_, GetServices(kTestAddr1)).WillOnce(Return(true)); |
| delegate->OnConnectChanged(kTestAddr1, true /* status */, |
| true /* connected */); |
| EXPECT_CALL(*observer_, OnConnectChanged(device, true)); |
| delegate->OnGetServices(kTestAddr1, {}); |
| |
| EXPECT_TRUE(device->IsConnected()); |
| EXPECT_TRUE(gatt_client_manager_->IsConnectedLeDevice(kTestAddr1)); |
| |
| base::MockCallback< |
| base::OnceCallback<void(std::vector<scoped_refptr<RemoteDevice>>)>> |
| get_connected_callback; |
| const std::vector<scoped_refptr<RemoteDevice>> kExpectedDevices({device}); |
| EXPECT_CALL(get_connected_callback, Run(kExpectedDevices)); |
| gatt_client_manager_->GetConnectedDevices(get_connected_callback.Get()); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_CALL(*gatt_client_, Disconnect(kTestAddr1)).WillOnce(Return(true)); |
| device->Disconnect({}); |
| // Should declare device as not connected after disconnect starts |
| EXPECT_FALSE(device->IsConnected()); |
| |
| EXPECT_CALL(*observer_, OnConnectChanged(device, false)); |
| delegate->OnConnectChanged(kTestAddr1, true /* status */, |
| false /* connected */); |
| EXPECT_FALSE(device->IsConnected()); |
| EXPECT_FALSE(gatt_client_manager_->IsConnectedLeDevice(kTestAddr1)); |
| |
| task_environment_.RunUntilIdle(); |
| } |
| |
| TEST_F(GattClientManagerTest, RemoteDeviceBond) { |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| |
| scoped_refptr<RemoteDevice> device = GetDevice(kTestAddr1); |
| Connect(kTestAddr1); |
| EXPECT_FALSE(device->IsBonded()); |
| |
| // CreateBond fails in the initial request. |
| EXPECT_CALL(*gatt_client_, CreateBond(kTestAddr1)).WillOnce(Return(false)); |
| EXPECT_CALL(cb_, Run(false)); |
| device->CreateBond(cb_.Get()); |
| EXPECT_FALSE(device->IsBonded()); |
| |
| // CreateBond fails in the callback. |
| EXPECT_CALL(*gatt_client_, CreateBond(kTestAddr1)).WillOnce(Return(true)); |
| EXPECT_CALL(cb_, Run(false)); |
| device->CreateBond(cb_.Get()); |
| delegate->OnBondChanged(kTestAddr1, false /* status */, false /* bonded */); |
| EXPECT_FALSE(device->IsBonded()); |
| |
| // CreateBond succeeds. |
| EXPECT_CALL(*gatt_client_, CreateBond(kTestAddr1)).WillOnce(Return(true)); |
| device->CreateBond(cb_.Get()); |
| EXPECT_CALL(cb_, Run(true)); |
| delegate->OnBondChanged(kTestAddr1, true /* status */, true /* bonded */); |
| EXPECT_TRUE(device->IsBonded()); |
| |
| // Bond with an already bonded device should fail. |
| EXPECT_CALL(cb_, Run(false)); |
| device->CreateBond(cb_.Get()); |
| EXPECT_TRUE(device->IsBonded()); |
| |
| // RemoveBond succeeds. |
| EXPECT_CALL(*gatt_client_, RemoveBond(kTestAddr1)).WillOnce(Return(true)); |
| device->RemoveBond(cb_.Get()); |
| EXPECT_CALL(cb_, Run(true)); |
| delegate->OnBondChanged(kTestAddr1, false /* status */, false /* bonded */); |
| EXPECT_FALSE(device->IsBonded()); |
| |
| // RemoveBond from an unbonded device succeeds. |
| EXPECT_CALL(*gatt_client_, RemoveBond(kTestAddr1)).WillOnce(Return(true)); |
| device->RemoveBond(cb_.Get()); |
| EXPECT_CALL(cb_, Run(true)); |
| delegate->OnBondChanged(kTestAddr1, false /* status */, false /* bonded */); |
| EXPECT_FALSE(device->IsBonded()); |
| |
| // CreateBond again succeeds. |
| EXPECT_CALL(*gatt_client_, CreateBond(kTestAddr1)).WillOnce(Return(true)); |
| device->CreateBond(cb_.Get()); |
| EXPECT_CALL(cb_, Run(true)); |
| delegate->OnBondChanged(kTestAddr1, true /* status */, true /* bonded */); |
| EXPECT_TRUE(device->IsBonded()); |
| |
| // Device should remain bonded when it disconnects. |
| EXPECT_CALL(*gatt_client_, Disconnect(kTestAddr1)).WillOnce(Return(true)); |
| device->Disconnect({}); |
| delegate->OnConnectChanged(kTestAddr1, true /* status */, |
| false /* connected */); |
| EXPECT_FALSE(device->IsConnected()); |
| EXPECT_TRUE(device->IsBonded()); |
| |
| // RemoveBond from a disconnected but bonded device. |
| EXPECT_CALL(*gatt_client_, RemoveBond(kTestAddr1)).WillOnce(Return(true)); |
| device->RemoveBond(cb_.Get()); |
| EXPECT_CALL(cb_, Run(true)); |
| delegate->OnBondChanged(kTestAddr1, false /* status */, false /* bonded */); |
| EXPECT_FALSE(device->IsBonded()); |
| |
| task_environment_.RunUntilIdle(); |
| } |
| |
| TEST_F(GattClientManagerTest, RemoteDeviceBondedOnInitialization) { |
| // NotifyBonded at initialization. |
| gatt_client_manager_->NotifyBonded(kTestAddr1); |
| |
| // Device should have updated the bonding state. |
| scoped_refptr<RemoteDevice> device = GetDevice(kTestAddr1); |
| EXPECT_FALSE(device->IsConnected()); |
| EXPECT_TRUE(device->IsBonded()); |
| |
| Connect(kTestAddr1); |
| EXPECT_TRUE(device->IsConnected()); |
| EXPECT_TRUE(device->IsBonded()); |
| |
| task_environment_.RunUntilIdle(); |
| } |
| |
| TEST_F(GattClientManagerTest, RemoteDeviceConnectConcurrent) { |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| scoped_refptr<RemoteDevice> device1 = GetDevice(kTestAddr1); |
| scoped_refptr<RemoteDevice> device2 = GetDevice(kTestAddr2); |
| scoped_refptr<RemoteDevice> device3 = GetDevice(kTestAddr3); |
| scoped_refptr<RemoteDevice> device4 = GetDevice(kTestAddr4); |
| scoped_refptr<RemoteDevice> device5 = GetDevice(kTestAddr5); |
| |
| base::MockCallback<RemoteDevice::StatusCallback> cb1; |
| base::MockCallback<RemoteDevice::StatusCallback> cb2; |
| base::MockCallback<RemoteDevice::StatusCallback> cb3; |
| base::MockCallback<RemoteDevice::StatusCallback> cb4; |
| base::MockCallback<RemoteDevice::StatusCallback> cb5; |
| |
| // Device5 is already connected at the beginning. |
| Connect(kTestAddr5); |
| |
| // Only the 1st Connect request will be executed immediately. The rest will be |
| // queued. |
| EXPECT_CALL(*gatt_client_, Connect(kTestAddr1)).WillOnce(Return(true)); |
| device1->Connect(cb1.Get()); |
| device2->Connect(cb2.Get()); |
| device3->Connect(cb3.Get()); |
| device4->Connect(cb4.Get()); |
| device5->Disconnect(cb5.Get()); |
| |
| EXPECT_CALL(*gatt_client_, GetServices(kTestAddr1)).WillOnce(Return(true)); |
| delegate->OnConnectChanged(kTestAddr1, true /* status */, |
| true /* connected */); |
| |
| // Queued Connect requests will not be called until we receive OnGetServices |
| // of the current Connect request if it is successful. |
| EXPECT_CALL(cb1, Run(true)); |
| EXPECT_CALL(*gatt_client_, Connect(kTestAddr2)).WillOnce(Return(false)); |
| EXPECT_CALL(cb2, Run(false)); |
| // If the Connect request fails in the initial request (not in the callback), |
| // the next queued request will be executed immediately. |
| EXPECT_CALL(*gatt_client_, Connect(kTestAddr3)).WillOnce(Return(true)); |
| delegate->OnGetServices(kTestAddr1, {}); |
| |
| EXPECT_CALL(cb3, Run(false)); |
| EXPECT_CALL(*gatt_client_, Connect(kTestAddr4)).WillOnce(Return(true)); |
| delegate->OnConnectChanged(kTestAddr3, true /* status */, |
| false /* connected */); |
| |
| EXPECT_CALL(*gatt_client_, GetServices(kTestAddr4)).WillOnce(Return(true)); |
| delegate->OnConnectChanged(kTestAddr4, true /* status */, |
| true /* connected */); |
| |
| EXPECT_CALL(cb4, Run(true)); |
| EXPECT_CALL(*gatt_client_, Disconnect(kTestAddr5)).WillOnce(Return(true)); |
| delegate->OnGetServices(kTestAddr4, {}); |
| |
| EXPECT_CALL(cb5, Run(true)); |
| delegate->OnConnectChanged(kTestAddr5, true /* status */, |
| false /* connected */); |
| |
| EXPECT_TRUE(device1->IsConnected()); |
| EXPECT_FALSE(device2->IsConnected()); |
| EXPECT_FALSE(device3->IsConnected()); |
| EXPECT_TRUE(device4->IsConnected()); |
| EXPECT_FALSE(device5->IsConnected()); |
| |
| base::MockCallback<base::OnceCallback<void(size_t)>> |
| get_num_connected_callback; |
| EXPECT_CALL(get_num_connected_callback, Run(2)); |
| gatt_client_manager_->GetNumConnected(get_num_connected_callback.Get()); |
| |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(GattClientManagerTest, ConnectTimeout) { |
| scoped_refptr<RemoteDevice> device = GetDevice(kTestAddr1); |
| |
| // Issue a Connect request |
| EXPECT_CALL(*gatt_client_, Connect(kTestAddr1)).WillOnce(Return(true)); |
| device->Connect(cb_.Get()); |
| |
| // Let Connect request timeout |
| // We should expect to receive Connect failure message |
| EXPECT_CALL(*gatt_client_, ClearPendingConnect(kTestAddr1)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(cb_, Run(false)); |
| task_environment_.FastForwardBy(GattClientManagerImpl::kConnectTimeout); |
| EXPECT_FALSE(device->IsConnected()); |
| } |
| |
| TEST_F(GattClientManagerTest, GetServicesTimeout) { |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| |
| scoped_refptr<RemoteDevice> device = GetDevice(kTestAddr1); |
| |
| // Issue a Connect request and let Connect succeed |
| EXPECT_CALL(*gatt_client_, Connect(kTestAddr1)).WillOnce(Return(true)); |
| device->Connect(cb_.Get()); |
| EXPECT_CALL(*gatt_client_, GetServices(kTestAddr1)).WillOnce(Return(true)); |
| delegate->OnConnectChanged(kTestAddr1, true /* status */, |
| true /* connected */); |
| |
| // Let GetServices request timeout |
| // We should request a disconnect. |
| EXPECT_CALL(*gatt_client_, Disconnect(kTestAddr1)).WillOnce(Return(true)); |
| task_environment_.FastForwardBy(GattClientManagerImpl::kConnectTimeout); |
| |
| // Make sure we issued a disconnect. |
| testing::Mock::VerifyAndClearExpectations(gatt_client_.get()); |
| |
| EXPECT_CALL(cb_, Run(false)); |
| delegate->OnConnectChanged(kTestAddr1, true /* status */, |
| false /* connected */); |
| |
| EXPECT_FALSE(device->IsConnected()); |
| } |
| |
| TEST_F(GattClientManagerTest, RemoteDeviceReadRssi) { |
| static const int kRssi = -34; |
| |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| scoped_refptr<RemoteDevice> device = GetDevice(kTestAddr1); |
| |
| Connect(kTestAddr1); |
| base::MockCallback<RemoteDevice::RssiCallback> rssi_cb; |
| |
| // First ReadRemoteRssi request fails right away. |
| EXPECT_CALL(*gatt_client_, ReadRemoteRssi(kTestAddr1)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(rssi_cb, Run(false, 0)); |
| device->ReadRemoteRssi(rssi_cb.Get()); |
| |
| // Second ReadRemoteRssi request succeeds. |
| EXPECT_CALL(*gatt_client_, ReadRemoteRssi(kTestAddr1)).WillOnce(Return(true)); |
| device->ReadRemoteRssi(rssi_cb.Get()); |
| |
| EXPECT_CALL(rssi_cb, Run(true, kRssi)); |
| delegate->OnReadRemoteRssi(kTestAddr1, true /* status */, kRssi); |
| } |
| |
| TEST_F(GattClientManagerTest, DisconnectAll) { |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| base::MockCallback<GattClientManagerImpl::StatusCallback> cb; |
| |
| // No connected devices, DisconnectAll should be successful. |
| EXPECT_CALL(cb, Run(true)); |
| gatt_client_manager_->DisconnectAll(cb.Get()); |
| |
| scoped_refptr<RemoteDevice> device1 = GetDevice(kTestAddr1); |
| scoped_refptr<RemoteDevice> device2 = GetDevice(kTestAddr2); |
| scoped_refptr<RemoteDevice> device3 = GetDevice(kTestAddr3); |
| |
| // Connect all 3 devices. |
| Connect(kTestAddr1); |
| Connect(kTestAddr2); |
| Connect(kTestAddr3); |
| |
| // Disable GATT client connectability. |
| EXPECT_TRUE(gatt_client_manager_->SetGattClientConnectable(false)); |
| EXPECT_FALSE(gatt_client_manager_->gatt_client_connectable()); |
| |
| // Disconnect requests will be queued. |
| EXPECT_CALL(*gatt_client_, Disconnect(kTestAddr1)).WillOnce(Return(true)); |
| gatt_client_manager_->DisconnectAll(cb.Get()); |
| |
| // cb will be run when last device got disconnected. |
| EXPECT_CALL(*gatt_client_, Disconnect(kTestAddr2)).WillOnce(Return(true)); |
| delegate->OnConnectChanged(kTestAddr1, true /* status */, |
| false /* connected */); |
| EXPECT_CALL(*gatt_client_, Disconnect(kTestAddr3)).WillOnce(Return(true)); |
| delegate->OnConnectChanged(kTestAddr2, true /* status */, |
| false /* connected */); |
| |
| // Shouldn't be able to enable connectability when DisconnectAll is pending. |
| EXPECT_FALSE(gatt_client_manager_->SetGattClientConnectable(true)); |
| EXPECT_FALSE(gatt_client_manager_->gatt_client_connectable()); |
| |
| EXPECT_CALL(cb, Run(true)); |
| delegate->OnConnectChanged(kTestAddr3, true /* status */, |
| false /* connected */); |
| |
| base::MockCallback<base::OnceCallback<void(size_t)>> |
| get_num_connected_callback; |
| EXPECT_CALL(get_num_connected_callback, Run(0)); |
| gatt_client_manager_->GetNumConnected(get_num_connected_callback.Get()); |
| |
| // Re-enable connectability when DisconnectAll completes. |
| EXPECT_TRUE(gatt_client_manager_->SetGattClientConnectable(true)); |
| EXPECT_TRUE(gatt_client_manager_->gatt_client_connectable()); |
| |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(GattClientManagerTest, DisconnectAllTimeout) { |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| base::MockCallback<GattClientManagerImpl::StatusCallback> cb; |
| |
| scoped_refptr<RemoteDevice> device1 = GetDevice(kTestAddr1); |
| scoped_refptr<RemoteDevice> device2 = GetDevice(kTestAddr2); |
| Connect(kTestAddr1); |
| Connect(kTestAddr2); |
| |
| // Issue a DisconnectAll request. |
| EXPECT_CALL(*gatt_client_, Disconnect(kTestAddr1)).WillOnce(Return(true)); |
| gatt_client_manager_->DisconnectAll(cb.Get()); |
| |
| // Let the fist Disconnect request timeout |
| EXPECT_CALL(*gatt_client_, ClearPendingDisconnect(kTestAddr1)) |
| .WillOnce(Return(true)); |
| |
| // We should expect to receive DisconnectAll failure message |
| EXPECT_CALL(cb, Run(false)); |
| // Run second Disconnect request in the queue. |
| EXPECT_CALL(*gatt_client_, Disconnect(kTestAddr2)).WillOnce(Return(true)); |
| task_environment_.FastForwardBy(GattClientManagerImpl::kDisconnectTimeout); |
| |
| // We should treat device as disconnected for this unknown case |
| EXPECT_FALSE(device1->IsConnected()); |
| |
| // Second Disconnect request succeeds. |
| delegate->OnConnectChanged(kTestAddr2, true /* status */, |
| false /* connected */); |
| |
| base::MockCallback<base::OnceCallback<void(size_t)>> |
| get_num_connected_callback; |
| EXPECT_CALL(get_num_connected_callback, Run(0)); |
| gatt_client_manager_->GetNumConnected(get_num_connected_callback.Get()); |
| } |
| |
| TEST_F(GattClientManagerTest, Connectability) { |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| scoped_refptr<RemoteDevice> device = GetDevice(kTestAddr1); |
| |
| // By default GATT client is connectable. |
| EXPECT_TRUE(gatt_client_manager_->gatt_client_connectable()); |
| |
| // Start a connection. |
| EXPECT_CALL(*gatt_client_, Connect(kTestAddr1)).WillOnce(Return(true)); |
| device->Connect(cb_.Get()); |
| |
| // Disable GATT client connectability while connection is pending. |
| EXPECT_TRUE(gatt_client_manager_->SetGattClientConnectable(false)); |
| EXPECT_FALSE(gatt_client_manager_->gatt_client_connectable()); |
| |
| // Expect to disconnect after receiving the connect callback. |
| EXPECT_CALL(*gatt_client_, Disconnect(kTestAddr1)).WillOnce(Return(true)); |
| delegate->OnConnectChanged(kTestAddr1, true /* status */, |
| true /* connected */); |
| |
| EXPECT_CALL(cb_, Run(false)); |
| delegate->OnConnectChanged(kTestAddr1, true /* status */, |
| false /* connected */); |
| ASSERT_FALSE(device->IsConnected()); |
| |
| // Connect should fail when GATT client connectability is already disabled. |
| EXPECT_CALL(*gatt_client_, Connect(_)).Times(0); |
| EXPECT_CALL(cb_, Run(false)); |
| device->Connect(cb_.Get()); |
| ASSERT_FALSE(device->IsConnected()); |
| |
| // Re-enable connectability. |
| EXPECT_TRUE(gatt_client_manager_->SetGattClientConnectable(true)); |
| EXPECT_TRUE(gatt_client_manager_->gatt_client_connectable()); |
| |
| // Connect succeeds. |
| Connect(kTestAddr1); |
| |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(GattClientManagerTest, ReadRemoteRssiTimeout) { |
| static const int kRssi = -34; |
| |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| scoped_refptr<RemoteDevice> device = GetDevice(kTestAddr1); |
| |
| Connect(kTestAddr1); |
| |
| // Issue a ReadRemoteRssi request. |
| base::MockCallback<RemoteDevice::RssiCallback> rssi_cb; |
| EXPECT_CALL(*gatt_client_, ReadRemoteRssi(kTestAddr1)).WillOnce(Return(true)); |
| device->ReadRemoteRssi(rssi_cb.Get()); |
| |
| // Let ReadRemoteRssi request timeout. |
| // We should expect to receive ReadRemoteRssi failure message. |
| EXPECT_CALL(rssi_cb, Run(false, 0)); |
| task_environment_.FastForwardBy( |
| GattClientManagerImpl::kReadRemoteRssiTimeout); |
| |
| // The following callback should be ignored. |
| delegate->OnReadRemoteRssi(kTestAddr1, true /* status */, kRssi); |
| |
| // Device should remain connected. |
| EXPECT_TRUE(device->IsConnected()); |
| } |
| |
| TEST_F(GattClientManagerTest, RemoteDeviceReadRssiConcurrent) { |
| static const int kRssi1 = -34; |
| static const int kRssi3 = -68; |
| |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| scoped_refptr<RemoteDevice> device1 = GetDevice(kTestAddr1); |
| scoped_refptr<RemoteDevice> device2 = GetDevice(kTestAddr2); |
| scoped_refptr<RemoteDevice> device3 = GetDevice(kTestAddr3); |
| |
| base::MockCallback<RemoteDevice::RssiCallback> rssi_cb1; |
| base::MockCallback<RemoteDevice::RssiCallback> rssi_cb2; |
| base::MockCallback<RemoteDevice::RssiCallback> rssi_cb3; |
| |
| // Only the 1st ReadRemoteRssi request will be executed immediately. The rest |
| // will be queued. |
| EXPECT_CALL(*gatt_client_, ReadRemoteRssi(kTestAddr1)).WillOnce(Return(true)); |
| device1->ReadRemoteRssi(rssi_cb1.Get()); |
| device2->ReadRemoteRssi(rssi_cb2.Get()); |
| device3->ReadRemoteRssi(rssi_cb3.Get()); |
| |
| // Queued ReadRemoteRssi requests will not be called until we receive |
| // OnGetServices of the current Connect request if it is successful. |
| EXPECT_CALL(rssi_cb1, Run(true, kRssi1)); |
| EXPECT_CALL(*gatt_client_, ReadRemoteRssi(kTestAddr2)) |
| .WillOnce(Return(false)); |
| EXPECT_CALL(rssi_cb2, Run(false, _)); |
| // If the ReadRemoteRssi request fails in the initial request (not in the |
| // callback), the next queued request will be executed immediately. |
| EXPECT_CALL(*gatt_client_, ReadRemoteRssi(kTestAddr3)).WillOnce(Return(true)); |
| delegate->OnReadRemoteRssi(kTestAddr1, true, kRssi1); |
| |
| EXPECT_CALL(rssi_cb3, Run(true, kRssi3)); |
| delegate->OnReadRemoteRssi(kTestAddr3, true, kRssi3); |
| } |
| |
| TEST_F(GattClientManagerTest, RemoteDeviceRequestMtu) { |
| static const int kMtu = 512; |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| scoped_refptr<RemoteDevice> device = GetDevice(kTestAddr1); |
| Connect(kTestAddr1); |
| |
| EXPECT_EQ(RemoteDevice::kDefaultMtu, device->GetMtu()); |
| EXPECT_CALL(*gatt_client_, RequestMtu(kTestAddr1, kMtu)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(cb_, Run(true)); |
| device->RequestMtu(kMtu, cb_.Get()); |
| EXPECT_CALL(*observer_, OnMtuChanged(device, kMtu)); |
| delegate->OnMtuChanged(kTestAddr1, true, kMtu); |
| EXPECT_EQ(kMtu, device->GetMtu()); |
| task_environment_.RunUntilIdle(); |
| } |
| |
| TEST_F(GattClientManagerTest, RemoteDeviceConnectionParameterUpdate) { |
| const int kMinInterval = 10; |
| const int kMaxInterval = 10; |
| const int kLatency = 50; |
| const int kTimeout = 100; |
| |
| Connect(kTestAddr1); |
| |
| scoped_refptr<RemoteDevice> device = GetDevice(kTestAddr1); |
| EXPECT_CALL(*gatt_client_, |
| ConnectionParameterUpdate(kTestAddr1, kMinInterval, kMaxInterval, |
| kLatency, kTimeout)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(cb_, Run(true)); |
| device->ConnectionParameterUpdate(kMinInterval, kMaxInterval, kLatency, |
| kTimeout, cb_.Get()); |
| } |
| |
| TEST_F(GattClientManagerTest, RemoteDeviceServices) { |
| const auto kServices = GenerateServices(); |
| Connect(kTestAddr1); |
| scoped_refptr<RemoteDevice> device = GetDevice(kTestAddr1); |
| std::vector<scoped_refptr<RemoteService>> services; |
| EXPECT_EQ(0ul, GetServices(device.get()).size()); |
| |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| delegate->OnServicesAdded(kTestAddr1, kServices); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_EQ(kServices.size(), GetServices(device.get()).size()); |
| for (const auto& service : kServices) { |
| scoped_refptr<RemoteService> remote_service = |
| GetServiceByUuid(device.get(), service.uuid); |
| ASSERT_TRUE(remote_service); |
| EXPECT_EQ(service.uuid, remote_service->uuid()); |
| EXPECT_EQ(service.handle, remote_service->handle()); |
| EXPECT_EQ(service.primary, remote_service->primary()); |
| EXPECT_EQ(service.characteristics.size(), |
| remote_service->GetCharacteristics().size()); |
| |
| for (const auto& characteristic : service.characteristics) { |
| scoped_refptr<RemoteCharacteristic> remote_char = |
| remote_service->GetCharacteristicByUuid(characteristic.uuid); |
| ASSERT_TRUE(remote_char); |
| EXPECT_EQ(characteristic.uuid, remote_char->uuid()); |
| EXPECT_EQ(characteristic.handle, remote_char->handle()); |
| EXPECT_EQ(characteristic.permissions, remote_char->permissions()); |
| EXPECT_EQ(characteristic.properties, remote_char->properties()); |
| EXPECT_EQ(characteristic.descriptors.size(), |
| remote_char->GetDescriptors().size()); |
| |
| for (const auto& descriptor : characteristic.descriptors) { |
| scoped_refptr<RemoteDescriptor> remote_desc = |
| remote_char->GetDescriptorByUuid(descriptor.uuid); |
| ASSERT_TRUE(remote_desc); |
| EXPECT_EQ(descriptor.uuid, remote_desc->uuid()); |
| EXPECT_EQ(descriptor.handle, remote_desc->handle()); |
| EXPECT_EQ(descriptor.permissions, remote_desc->permissions()); |
| } |
| } |
| } |
| } |
| |
| TEST_F(GattClientManagerTest, RemoteDeviceCharacteristic) { |
| const std::vector<uint8_t> kTestData1 = {0x1, 0x2, 0x3}; |
| const std::vector<uint8_t> kTestData2 = {0x4, 0x5, 0x6}; |
| const std::vector<uint8_t> kTestData3 = {0x7, 0x8, 0x9}; |
| const auto kServices = GenerateServices(); |
| const bluetooth_v2_shlib::Gatt::Client::AuthReq kAuthReq = |
| bluetooth_v2_shlib::Gatt::Client::AUTH_REQ_MITM; |
| const bluetooth_v2_shlib::Gatt::WriteType kWriteType = |
| bluetooth_v2_shlib::Gatt::WRITE_TYPE_DEFAULT; |
| |
| Connect(kTestAddr1); |
| scoped_refptr<RemoteDevice> device = GetDevice(kTestAddr1); |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| delegate->OnServicesAdded(kTestAddr1, kServices); |
| std::vector<scoped_refptr<RemoteService>> services = |
| GetServices(device.get()); |
| ASSERT_EQ(kServices.size(), services.size()); |
| |
| auto service = services[0]; |
| std::vector<scoped_refptr<RemoteCharacteristic>> characteristics = |
| service->GetCharacteristics(); |
| ASSERT_GE(characteristics.size(), 1ul); |
| ASSERT_TRUE(characteristics[0]); |
| auto* characteristic = |
| static_cast<RemoteCharacteristicImpl*>(characteristics[0].get()); |
| |
| EXPECT_CALL(*gatt_client_, |
| WriteCharacteristic(kTestAddr1, characteristic->characteristic(), |
| kAuthReq, kWriteType, kTestData1)) |
| .WillOnce(Return(true)); |
| |
| EXPECT_CALL(cb_, Run(true)); |
| characteristic->WriteAuth(kAuthReq, kWriteType, kTestData1, cb_.Get()); |
| delegate->OnCharacteristicWriteResponse(kTestAddr1, true, |
| characteristic->handle()); |
| |
| EXPECT_CALL(*gatt_client_, |
| ReadCharacteristic(kTestAddr1, characteristic->characteristic(), |
| kAuthReq)) |
| .WillOnce(Return(true)); |
| |
| base::MockCallback<RemoteCharacteristic::ReadCallback> read_cb; |
| EXPECT_CALL(read_cb, Run(true, kTestData2)); |
| characteristic->ReadAuth(kAuthReq, read_cb.Get()); |
| delegate->OnCharacteristicReadResponse(kTestAddr1, true, |
| characteristic->handle(), kTestData2); |
| |
| EXPECT_CALL(*gatt_client_, |
| SetCharacteristicNotification( |
| kTestAddr1, characteristic->characteristic(), true)) |
| .WillOnce(Return(true)); |
| |
| EXPECT_CALL(cb_, Run(true)); |
| characteristic->SetNotification(true, cb_.Get()); |
| |
| EXPECT_CALL(*observer_, OnCharacteristicNotification( |
| device, characteristics[0], kTestData3)); |
| delegate->OnNotification(kTestAddr1, characteristic->handle(), kTestData3); |
| task_environment_.RunUntilIdle(); |
| } |
| |
| TEST_F(GattClientManagerTest, |
| RemoteDeviceCharacteristicSetRegisterNotification) { |
| const std::vector<uint8_t> kTestData1 = {0x1, 0x2, 0x3}; |
| const auto kServices = GenerateServices(); |
| Connect(kTestAddr1); |
| scoped_refptr<RemoteDevice> device = GetDevice(kTestAddr1); |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| delegate->OnServicesAdded(kTestAddr1, kServices); |
| std::vector<scoped_refptr<RemoteService>> services = |
| GetServices(device.get()); |
| ASSERT_EQ(kServices.size(), services.size()); |
| |
| scoped_refptr<RemoteService> service = services[0]; |
| std::vector<scoped_refptr<RemoteCharacteristic>> characteristics = |
| service->GetCharacteristics(); |
| ASSERT_GE(characteristics.size(), 1ul); |
| ASSERT_TRUE(characteristics[0]); |
| RemoteCharacteristicImpl* characteristic = |
| static_cast<RemoteCharacteristicImpl*>(characteristics[0].get()); |
| |
| scoped_refptr<RemoteDescriptor> cccd = |
| characteristic->GetDescriptorByUuid(RemoteDescriptor::kCccdUuid); |
| ASSERT_TRUE(cccd); |
| |
| EXPECT_CALL(*gatt_client_, |
| SetCharacteristicNotification( |
| kTestAddr1, characteristic->characteristic(), true)) |
| .WillOnce(Return(true)); |
| std::vector<uint8_t> cccd_enable_notification = { |
| std::begin(bluetooth::RemoteDescriptor::kEnableNotificationValue), |
| std::end(bluetooth::RemoteDescriptor::kEnableNotificationValue)}; |
| EXPECT_CALL(*gatt_client_, |
| WriteDescriptor( |
| kTestAddr1, |
| static_cast<RemoteDescriptorImpl*>(cccd.get())->descriptor(), |
| _, cccd_enable_notification)) |
| .WillOnce(Return(true)); |
| |
| characteristic->SetRegisterNotification(true, cb_.Get()); |
| EXPECT_CALL(cb_, Run(true)); |
| delegate->OnDescriptorWriteResponse(kTestAddr1, true, cccd->handle()); |
| |
| EXPECT_CALL(*observer_, OnCharacteristicNotification( |
| device, characteristics[0], kTestData1)); |
| delegate->OnNotification(kTestAddr1, characteristic->handle(), kTestData1); |
| task_environment_.RunUntilIdle(); |
| } |
| |
| TEST_F(GattClientManagerTest, RemoteDeviceCharacteristicSetRegisterIndication) { |
| const std::vector<uint8_t> kTestData1 = {0x1, 0x2, 0x3}; |
| const auto kServices = GenerateServices(); |
| Connect(kTestAddr1); |
| scoped_refptr<RemoteDevice> device = GetDevice(kTestAddr1); |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| delegate->OnServicesAdded(kTestAddr1, kServices); |
| std::vector<scoped_refptr<RemoteService>> services = |
| GetServices(device.get()); |
| ASSERT_EQ(kServices.size(), services.size()); |
| |
| scoped_refptr<RemoteService> service = services[0]; |
| std::vector<scoped_refptr<RemoteCharacteristic>> characteristics = |
| service->GetCharacteristics(); |
| ASSERT_EQ(characteristics.size(), 4ul); |
| |
| // |characteristics[2]| supports indication only. |
| ASSERT_TRUE(characteristics[2]); |
| RemoteCharacteristicImpl* characteristic = |
| static_cast<RemoteCharacteristicImpl*>(characteristics[2].get()); |
| |
| scoped_refptr<RemoteDescriptor> cccd = |
| characteristic->GetDescriptorByUuid(RemoteDescriptor::kCccdUuid); |
| ASSERT_TRUE(cccd); |
| |
| EXPECT_CALL(*gatt_client_, |
| SetCharacteristicNotification( |
| kTestAddr1, characteristic->characteristic(), true)) |
| .WillOnce(Return(true)); |
| std::vector<uint8_t> cccd_enable_indication = { |
| std::begin(bluetooth::RemoteDescriptor::kEnableIndicationValue), |
| std::end(bluetooth::RemoteDescriptor::kEnableIndicationValue)}; |
| EXPECT_CALL(*gatt_client_, |
| WriteDescriptor( |
| kTestAddr1, |
| static_cast<RemoteDescriptorImpl*>(cccd.get())->descriptor(), |
| _, cccd_enable_indication)) |
| .WillOnce(Return(true)); |
| |
| characteristic->SetRegisterNotificationOrIndication(true, cb_.Get()); |
| EXPECT_CALL(cb_, Run(true)); |
| delegate->OnDescriptorWriteResponse(kTestAddr1, true, cccd->handle()); |
| |
| EXPECT_CALL(*observer_, OnCharacteristicNotification( |
| device, characteristics[2], kTestData1)); |
| delegate->OnNotification(kTestAddr1, characteristic->handle(), kTestData1); |
| task_environment_.RunUntilIdle(); |
| |
| // |characteristics[3]| supports both notification and indication. |
| ASSERT_TRUE(characteristics[3]); |
| characteristic = |
| static_cast<RemoteCharacteristicImpl*>(characteristics[3].get()); |
| |
| cccd = characteristic->GetDescriptorByUuid(RemoteDescriptor::kCccdUuid); |
| ASSERT_TRUE(cccd); |
| |
| // Notification has higher priority than indication. So |
| // SetRegisterNotificationOrIndication will behave the same as |
| // SetRegisterNotification. |
| EXPECT_CALL(*gatt_client_, |
| SetCharacteristicNotification( |
| kTestAddr1, characteristic->characteristic(), true)) |
| .WillOnce(Return(true)); |
| std::vector<uint8_t> cccd_enable_notification = { |
| std::begin(bluetooth::RemoteDescriptor::kEnableNotificationValue), |
| std::end(bluetooth::RemoteDescriptor::kEnableNotificationValue)}; |
| EXPECT_CALL(*gatt_client_, |
| WriteDescriptor( |
| kTestAddr1, |
| static_cast<RemoteDescriptorImpl*>(cccd.get())->descriptor(), |
| _, cccd_enable_notification)) |
| .WillOnce(Return(true)); |
| |
| characteristic->SetRegisterNotificationOrIndication(true, cb_.Get()); |
| EXPECT_CALL(cb_, Run(true)); |
| delegate->OnDescriptorWriteResponse(kTestAddr1, true, cccd->handle()); |
| |
| EXPECT_CALL(*observer_, OnCharacteristicNotification( |
| device, characteristics[3], kTestData1)); |
| delegate->OnNotification(kTestAddr1, characteristic->handle(), kTestData1); |
| task_environment_.RunUntilIdle(); |
| } |
| |
| TEST_F(GattClientManagerTest, RemoteDeviceDescriptor) { |
| const std::vector<uint8_t> kTestData1 = {0x1, 0x2, 0x3}; |
| const std::vector<uint8_t> kTestData2 = {0x4, 0x5, 0x6}; |
| const bluetooth_v2_shlib::Gatt::Client::AuthReq kAuthReq = |
| bluetooth_v2_shlib::Gatt::Client::AUTH_REQ_MITM; |
| const auto kServices = GenerateServices(); |
| Connect(kTestAddr1); |
| scoped_refptr<RemoteDevice> device = GetDevice(kTestAddr1); |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| delegate->OnServicesAdded(kTestAddr1, kServices); |
| std::vector<scoped_refptr<RemoteService>> services = |
| GetServices(device.get()); |
| ASSERT_EQ(kServices.size(), services.size()); |
| |
| auto service = services[0]; |
| std::vector<scoped_refptr<RemoteCharacteristic>> characteristics = |
| service->GetCharacteristics(); |
| ASSERT_GE(characteristics.size(), 1ul); |
| auto characteristic = characteristics[0]; |
| |
| std::vector<scoped_refptr<RemoteDescriptor>> descriptors = |
| characteristic->GetDescriptors(); |
| ASSERT_GE(descriptors.size(), 1ul); |
| ASSERT_TRUE(descriptors[0]); |
| auto* descriptor = static_cast<RemoteDescriptorImpl*>(descriptors[0].get()); |
| |
| EXPECT_CALL(*gatt_client_, |
| WriteDescriptor(kTestAddr1, descriptor->descriptor(), kAuthReq, |
| kTestData1)) |
| .WillOnce(Return(true)); |
| |
| EXPECT_CALL(cb_, Run(true)); |
| descriptor->WriteAuth(kAuthReq, kTestData1, cb_.Get()); |
| delegate->OnDescriptorWriteResponse(kTestAddr1, true, descriptor->handle()); |
| |
| EXPECT_CALL(*gatt_client_, |
| ReadDescriptor(kTestAddr1, descriptor->descriptor(), kAuthReq)) |
| .WillOnce(Return(true)); |
| |
| base::MockCallback<RemoteDescriptor::ReadCallback> read_cb; |
| EXPECT_CALL(read_cb, Run(true, kTestData2)); |
| descriptor->ReadAuth(kAuthReq, read_cb.Get()); |
| delegate->OnDescriptorReadResponse(kTestAddr1, true, descriptor->handle(), |
| kTestData2); |
| } |
| |
| TEST_F(GattClientManagerTest, FakeCccd) { |
| std::vector<bluetooth_v2_shlib::Gatt::Service> input_services(1); |
| input_services[0].uuid = {{0x1}}; |
| input_services[0].handle = 0x1; |
| input_services[0].primary = true; |
| |
| bluetooth_v2_shlib::Gatt::Characteristic input_characteristic; |
| input_characteristic.uuid = {{0x1, 0x1}}; |
| input_characteristic.handle = 0x2; |
| input_characteristic.permissions = bluetooth_v2_shlib::Gatt::PERMISSION_READ; |
| input_characteristic.properties = bluetooth_v2_shlib::Gatt::PROPERTY_NOTIFY; |
| input_services[0].characteristics.push_back(input_characteristic); |
| |
| // Test indicate as well |
| input_characteristic.uuid = {{0x1, 0x2}}; |
| input_characteristic.handle = 0x3; |
| input_characteristic.properties = bluetooth_v2_shlib::Gatt::PROPERTY_INDICATE; |
| input_services[0].characteristics.push_back(input_characteristic); |
| |
| Connect(kTestAddr1); |
| scoped_refptr<RemoteDevice> device = GetDevice(kTestAddr1); |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| delegate->OnServicesAdded(kTestAddr1, input_services); |
| std::vector<scoped_refptr<RemoteService>> services = |
| GetServices(device.get()); |
| ASSERT_EQ(input_services.size(), services.size()); |
| |
| auto service = services[0]; |
| std::vector<scoped_refptr<RemoteCharacteristic>> characteristics = |
| service->GetCharacteristics(); |
| ASSERT_EQ(2u, characteristics.size()); |
| for (const auto& characteristic : characteristics) { |
| // A CCCD should have been created. |
| std::vector<scoped_refptr<RemoteDescriptor>> descriptors = |
| characteristic->GetDescriptors(); |
| ASSERT_EQ(descriptors.size(), 1ul); |
| auto descriptor = descriptors[0]; |
| EXPECT_EQ(RemoteDescriptor::kCccdUuid, descriptor->uuid()); |
| EXPECT_EQ(static_cast<bluetooth_v2_shlib::Gatt::Permissions>( |
| bluetooth_v2_shlib::Gatt::PERMISSION_READ | |
| bluetooth_v2_shlib::Gatt::PERMISSION_WRITE), |
| descriptor->permissions()); |
| } |
| } |
| |
| TEST_F(GattClientManagerTest, WriteType) { |
| const std::vector<uint8_t> kTestData1 = {0x1, 0x2, 0x3}; |
| |
| bluetooth_v2_shlib::Gatt::Service service; |
| |
| service.uuid = {{0x1}}; |
| service.handle = 0x1; |
| service.primary = true; |
| |
| { |
| bluetooth_v2_shlib::Gatt::Characteristic characteristic; |
| characteristic.uuid = {{0x1, 0x1}}; |
| characteristic.handle = 0x2; |
| characteristic.permissions = bluetooth_v2_shlib::Gatt::PERMISSION_WRITE; |
| characteristic.properties = bluetooth_v2_shlib::Gatt::PROPERTY_WRITE; |
| service.characteristics.push_back(characteristic); |
| |
| characteristic.uuid = {{0x1, 0x2}}; |
| characteristic.handle = 0x3; |
| characteristic.permissions = bluetooth_v2_shlib::Gatt::PERMISSION_WRITE; |
| characteristic.properties = |
| bluetooth_v2_shlib::Gatt::PROPERTY_WRITE_NO_RESPONSE; |
| service.characteristics.push_back(characteristic); |
| |
| characteristic.uuid = {{0x1, 0x3}}; |
| characteristic.handle = 0x4; |
| characteristic.permissions = bluetooth_v2_shlib::Gatt::PERMISSION_WRITE; |
| characteristic.properties = bluetooth_v2_shlib::Gatt::PROPERTY_SIGNED_WRITE; |
| service.characteristics.push_back(characteristic); |
| } |
| |
| Connect(kTestAddr1); |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| delegate->OnServicesAdded(kTestAddr1, {service}); |
| |
| scoped_refptr<RemoteDevice> device = GetDevice(kTestAddr1); |
| |
| std::vector<scoped_refptr<RemoteService>> services = |
| GetServices(device.get()); |
| ASSERT_EQ(1u, services.size()); |
| |
| std::vector<scoped_refptr<RemoteCharacteristic>> characteristics = |
| services[0]->GetCharacteristics(); |
| ASSERT_EQ(3u, characteristics.size()); |
| |
| using WriteType = bluetooth_v2_shlib::Gatt::WriteType; |
| |
| // The current implementation of RemoteDevice will put the characteristics in |
| // the order reported by libcast_bluetooth. |
| const WriteType kWriteTypes[] = {WriteType::WRITE_TYPE_DEFAULT, |
| WriteType::WRITE_TYPE_NO_RESPONSE, |
| WriteType::WRITE_TYPE_SIGNED}; |
| |
| for (size_t i = 0; i < characteristics.size(); ++i) { |
| ASSERT_TRUE(characteristics[i]); |
| auto* characteristic = |
| static_cast<RemoteCharacteristicImpl*>(characteristics[i].get()); |
| EXPECT_CALL( |
| *gatt_client_, |
| WriteCharacteristic(kTestAddr1, characteristic->characteristic(), |
| bluetooth_v2_shlib::Gatt::Client::AUTH_REQ_NONE, |
| kWriteTypes[i], kTestData1)) |
| .WillOnce(Return(true)); |
| |
| base::MockCallback<RemoteCharacteristic::StatusCallback> write_cb; |
| EXPECT_CALL(write_cb, Run(true)); |
| characteristic->Write(kTestData1, write_cb.Get()); |
| delegate->OnCharacteristicWriteResponse(kTestAddr1, true, |
| characteristic->handle()); |
| } |
| } |
| |
| TEST_F(GattClientManagerTest, ConnectMultiple) { |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| scoped_refptr<RemoteDevice> device = GetDevice(kTestAddr1); |
| for (size_t i = 0; i < 5; ++i) { |
| Connect(kTestAddr1); |
| EXPECT_TRUE(device->IsConnected()); |
| EXPECT_CALL(*gatt_client_, Disconnect(kTestAddr1)).WillOnce(Return(true)); |
| device->Disconnect({}); |
| delegate->OnConnectChanged(kTestAddr1, true /* status */, |
| false /* connected */); |
| EXPECT_FALSE(device->IsConnected()); |
| } |
| } |
| |
| TEST_F(GattClientManagerTest, GetServicesFailOnConnect) { |
| scoped_refptr<RemoteDevice> device = GetDevice(kTestAddr1); |
| EXPECT_CALL(*gatt_client_, Connect(kTestAddr1)).WillOnce(Return(true)); |
| device->Connect(cb_.Get()); |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| |
| EXPECT_CALL(cb_, Run(false)); |
| EXPECT_CALL(*gatt_client_, GetServices(kTestAddr1)).WillOnce(Return(false)); |
| delegate->OnConnectChanged(kTestAddr1, true /* status */, |
| true /* connected */); |
| EXPECT_FALSE(device->IsConnected()); |
| } |
| |
| TEST_F(GattClientManagerTest, GetServicesSuccessAfterConnectCallback) { |
| const auto kServices = GenerateServices(); |
| scoped_refptr<RemoteDevice> device = GetDevice(kTestAddr1); |
| |
| // Callback that checks when Connect()'s callback returns, GetServices returns |
| // the correct services. |
| bool cb_called = false; |
| auto cb = base::BindOnce( |
| [](GattClientManagerTest* gcmt, |
| const std::vector<bluetooth_v2_shlib::Gatt::Service>* |
| expected_services, |
| bool* cb_called, bool success) { |
| EXPECT_TRUE(success); |
| *cb_called = true; |
| |
| auto device = gcmt->GetDevice(kTestAddr1); |
| auto services = gcmt->GetServices(device.get()); |
| EXPECT_EQ(expected_services->size(), services.size()); |
| }, |
| this, &kServices, &cb_called); |
| EXPECT_CALL(*gatt_client_, Connect(kTestAddr1)).WillOnce(Return(true)); |
| device->Connect(std::move(cb)); |
| |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| EXPECT_CALL(*gatt_client_, GetServices(kTestAddr1)).WillOnce(Return(true)); |
| delegate->OnConnectChanged(kTestAddr1, true /* status */, |
| true /* connected */); |
| |
| // Connect's callback should not be called until service discovery is |
| // complete. |
| EXPECT_FALSE(cb_called); |
| delegate->OnGetServices(kTestAddr1, kServices); |
| EXPECT_TRUE(cb_called); |
| } |
| |
| TEST_F(GattClientManagerTest, Queuing) { |
| const std::vector<uint8_t> kTestData1 = {0x1, 0x2, 0x3}; |
| const std::vector<uint8_t> kTestData2 = {0x4, 0x5, 0x6}; |
| const std::vector<uint8_t> kTestData3 = {0x7, 0x8, 0x9}; |
| const auto kServices = GenerateServices(); |
| const bluetooth_v2_shlib::Gatt::Client::AuthReq kAuthReq = |
| bluetooth_v2_shlib::Gatt::Client::AUTH_REQ_MITM; |
| const bluetooth_v2_shlib::Gatt::WriteType kWriteType = |
| bluetooth_v2_shlib::Gatt::WRITE_TYPE_DEFAULT; |
| |
| Connect(kTestAddr1); |
| scoped_refptr<RemoteDevice> device = GetDevice(kTestAddr1); |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| delegate->OnServicesAdded(kTestAddr1, kServices); |
| std::vector<scoped_refptr<RemoteService>> services = |
| GetServices(device.get()); |
| ASSERT_EQ(kServices.size(), services.size()); |
| |
| auto service = services[0]; |
| std::vector<scoped_refptr<RemoteCharacteristic>> characteristics = |
| service->GetCharacteristics(); |
| ASSERT_GE(characteristics.size(), 2ul); |
| ASSERT_TRUE(characteristics[0]); |
| ASSERT_TRUE(characteristics[1]); |
| auto* characteristic1 = |
| static_cast<RemoteCharacteristicImpl*>(characteristics[0].get()); |
| auto* characteristic2 = |
| static_cast<RemoteCharacteristicImpl*>(characteristics[1].get()); |
| |
| // Issue a write to one characteristic. |
| EXPECT_CALL(*gatt_client_, |
| WriteCharacteristic(kTestAddr1, characteristic1->characteristic(), |
| kAuthReq, kWriteType, kTestData1)) |
| .WillOnce(Return(true)); |
| characteristic1->WriteAuth(kAuthReq, kWriteType, kTestData1, cb_.Get()); |
| |
| // Issue a read to another characteristic. The shlib should not get the call |
| // until after the read's callback. |
| EXPECT_CALL(*gatt_client_, |
| ReadCharacteristic(kTestAddr1, characteristic2->characteristic(), |
| kAuthReq)) |
| .Times(0); |
| base::MockCallback<RemoteCharacteristic::ReadCallback> read_cb; |
| characteristic2->ReadAuth(kAuthReq, read_cb.Get()); |
| |
| EXPECT_CALL(cb_, Run(true)); |
| EXPECT_CALL(*gatt_client_, |
| ReadCharacteristic(kTestAddr1, characteristic2->characteristic(), |
| kAuthReq)) |
| .WillOnce(Return(true)); |
| delegate->OnCharacteristicWriteResponse(kTestAddr1, true, |
| characteristic1->handle()); |
| |
| EXPECT_CALL(read_cb, Run(true, kTestData2)); |
| delegate->OnCharacteristicReadResponse(kTestAddr1, true, |
| characteristic2->handle(), kTestData2); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| TEST_F(GattClientManagerTest, CommandTimeout) { |
| const std::vector<uint8_t> kTestData = {0x7, 0x8, 0x9}; |
| const auto kServices = GenerateServices(); |
| const auto kAuthReq = bluetooth_v2_shlib::Gatt::Client::AUTH_REQ_MITM; |
| const auto kWriteType = bluetooth_v2_shlib::Gatt::WRITE_TYPE_DEFAULT; |
| |
| // Connect a device and get services. |
| Connect(kTestAddr1); |
| scoped_refptr<RemoteDevice> device = GetDevice(kTestAddr1); |
| bluetooth_v2_shlib::Gatt::Client::Delegate* delegate = |
| gatt_client_->delegate(); |
| delegate->OnServicesAdded(kTestAddr1, kServices); |
| std::vector<scoped_refptr<RemoteService>> services = |
| GetServices(device.get()); |
| ASSERT_EQ(kServices.size(), services.size()); |
| |
| auto service = services[0]; |
| std::vector<scoped_refptr<RemoteCharacteristic>> characteristics = |
| service->GetCharacteristics(); |
| ASSERT_GE(characteristics.size(), 1ul); |
| ASSERT_TRUE(characteristics[0]); |
| auto* characteristic = |
| static_cast<RemoteCharacteristicImpl*>(characteristics[0].get()); |
| |
| // Issue a write to one characteristic. |
| EXPECT_CALL(*gatt_client_, |
| WriteCharacteristic(kTestAddr1, characteristic->characteristic(), |
| kAuthReq, kWriteType, kTestData)) |
| .WillOnce(Return(true)); |
| characteristic->WriteAuth(kAuthReq, kWriteType, kTestData, cb_.Get()); |
| |
| // Let the command timeout |
| // We should request a disconnect. |
| EXPECT_CALL(*gatt_client_, Disconnect(kTestAddr1)).WillOnce(Return(true)); |
| task_environment_.FastForwardBy(RemoteDeviceImpl::kCommandTimeout); |
| |
| // Make sure we issued a disconnect. |
| testing::Mock::VerifyAndClearExpectations(gatt_client_.get()); |
| |
| // The operation should fail. |
| EXPECT_CALL(cb_, Run(false)); |
| delegate->OnConnectChanged(kTestAddr1, true /* status */, |
| false /* connected */); |
| } |
| |
| } // namespace bluetooth |
| } // namespace chromecast |