| // Copyright 2020 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/adapter.h" |
| |
| #include "base/callback_helpers.h" |
| #include "base/run_loop.h" |
| #include "base/task/thread_pool.h" |
| #include "base/test/bind.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "base/test/task_environment.h" |
| #include "build/build_config.h" |
| #include "device/bluetooth/bluetooth_advertisement.h" |
| #include "device/bluetooth/test/mock_bluetooth_adapter.h" |
| #include "device/bluetooth/test/mock_bluetooth_advertisement.h" |
| #include "device/bluetooth/test/mock_bluetooth_device.h" |
| #include "device/bluetooth/test/mock_bluetooth_socket.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using base::test::RunOnceCallback; |
| |
| using testing::_; |
| using testing::DoAll; |
| using testing::InvokeWithoutArgs; |
| using testing::NiceMock; |
| using testing::Return; |
| |
| namespace { |
| |
| const char kKnownDeviceAddress[] = "00:00:00:00:01"; |
| const char kUnknownDeviceAddress[] = "00:00:00:00:02"; |
| const char kServiceName[] = "ServiceName"; |
| const char kServiceId[] = "0000abcd-0000-0000-0000-000000000001"; |
| const char kDeviceServiceDataStr[] = "ServiceData"; |
| |
| std::vector<uint8_t> GetByteVector(const std::string& str) { |
| return std::vector<uint8_t>(str.begin(), str.end()); |
| } |
| |
| class MockBluetoothAdapterWithAdvertisements |
| : public device::MockBluetoothAdapter { |
| public: |
| void RegisterAdvertisement( |
| std::unique_ptr<device::BluetoothAdvertisement::Data> advertisement_data, |
| device::BluetoothAdapter::CreateAdvertisementCallback callback, |
| device::BluetoothAdapter::AdvertisementErrorCallback error_callback) |
| override { |
| last_advertisement_data_ = std::move(advertisement_data); |
| |
| if (should_advertisement_registration_succeed_) { |
| std::move(callback).Run( |
| base::MakeRefCounted<device::MockBluetoothAdvertisement>()); |
| } else { |
| std::move(error_callback) |
| .Run(device::BluetoothAdvertisement::ErrorCode:: |
| INVALID_ADVERTISEMENT_ERROR_CODE); |
| } |
| } |
| |
| bool should_advertisement_registration_succeed_ = true; |
| std::unique_ptr<device::BluetoothAdvertisement::Data> |
| last_advertisement_data_; |
| |
| protected: |
| ~MockBluetoothAdapterWithAdvertisements() override = default; |
| }; |
| |
| } // namespace |
| |
| namespace bluetooth { |
| |
| class AdapterTest : public testing::Test { |
| public: |
| AdapterTest() = default; |
| ~AdapterTest() override = default; |
| AdapterTest(const AdapterTest&) = delete; |
| AdapterTest& operator=(const AdapterTest&) = delete; |
| |
| void SetUp() override { |
| mock_bluetooth_adapter_ = base::MakeRefCounted< |
| NiceMock<MockBluetoothAdapterWithAdvertisements>>(); |
| ON_CALL(*mock_bluetooth_adapter_, IsPresent()).WillByDefault(Return(true)); |
| ON_CALL(*mock_bluetooth_adapter_, IsPowered()).WillByDefault(Return(true)); |
| |
| // |mock_known_bluetooth_device_| is a device found via discovery. |
| mock_known_bluetooth_device_ = |
| std::make_unique<testing::NiceMock<device::MockBluetoothDevice>>( |
| mock_bluetooth_adapter_.get(), |
| /*class=*/0, "Known Device", kKnownDeviceAddress, |
| /*paired=*/false, |
| /*connected=*/false); |
| // |mock_unknown_bluetooth_device_| is |connected| because it is created |
| // as a result of calling ConnectDevice(). |
| mock_unknown_bluetooth_device_ = |
| std::make_unique<testing::NiceMock<device::MockBluetoothDevice>>( |
| mock_bluetooth_adapter_.get(), |
| /*class=*/0, "Unknown Device", kUnknownDeviceAddress, |
| /*paired=*/false, |
| /*connected=*/true); |
| |
| // |mock_bluetooth_adapter_| can only find |mock_known_bluetooth_device_| |
| // via GetDevice(), not |mock_unknown_bluetooth_device_|. |
| ON_CALL(*mock_bluetooth_adapter_, GetDevice(kKnownDeviceAddress)) |
| .WillByDefault(Return(mock_known_bluetooth_device_.get())); |
| |
| mock_bluetooth_socket_ = |
| base::MakeRefCounted<NiceMock<device::MockBluetoothSocket>>(); |
| |
| adapter_ = std::make_unique<Adapter>(mock_bluetooth_adapter_); |
| } |
| |
| protected: |
| void RegisterAdvertisement(bool should_succeed, bool use_scan_data) { |
| mock_bluetooth_adapter_->should_advertisement_registration_succeed_ = |
| should_succeed; |
| |
| auto service_data = GetByteVector(kDeviceServiceDataStr); |
| mojo::Remote<mojom::Advertisement> advertisement; |
| |
| base::RunLoop run_loop; |
| adapter_->RegisterAdvertisement( |
| device::BluetoothUUID(kServiceId), service_data, |
| /*use_scan_data=*/use_scan_data, |
| base::BindLambdaForTesting([&](mojo::PendingRemote<mojom::Advertisement> |
| pending_advertisement) { |
| EXPECT_EQ(should_succeed, pending_advertisement.is_valid()); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| |
| void VerifyAdvertisement() { |
| auto service_data = GetByteVector(kDeviceServiceDataStr); |
| auto uuid_list = |
| mock_bluetooth_adapter_->last_advertisement_data_->service_uuids(); |
| EXPECT_EQ(1u, uuid_list->size()); |
| EXPECT_EQ(kServiceId, (*uuid_list)[0]); |
| auto last_service_data = |
| mock_bluetooth_adapter_->last_advertisement_data_->service_data(); |
| EXPECT_EQ(service_data, last_service_data->at(kServiceId)); |
| EXPECT_FALSE(mock_bluetooth_adapter_->last_advertisement_data_ |
| ->scan_response_data()); |
| } |
| |
| void VerifyAdvertisementWithScanData() { |
| auto service_data = GetByteVector(kDeviceServiceDataStr); |
| auto uuid_list = |
| mock_bluetooth_adapter_->last_advertisement_data_->service_uuids(); |
| EXPECT_EQ(1u, uuid_list->size()); |
| EXPECT_EQ(kServiceId, (*uuid_list)[0]); |
| EXPECT_FALSE( |
| mock_bluetooth_adapter_->last_advertisement_data_->service_data()); |
| auto last_scan_response_data = |
| mock_bluetooth_adapter_->last_advertisement_data_->scan_response_data(); |
| ASSERT_TRUE(base::Contains(*last_scan_response_data, 0x16)); |
| const auto& raw_data = (*last_scan_response_data)[0x16]; |
| // First two bytes should be the identifying bits of the kServiceId UUID. |
| // They should be in litten endian order (reversed). |
| EXPECT_EQ(0xCD, raw_data[0]); |
| EXPECT_EQ(0xAB, raw_data[1]); |
| EXPECT_EQ(service_data, |
| std::vector<uint8_t>(raw_data.begin() + 2, raw_data.end())); |
| } |
| |
| scoped_refptr<NiceMock<MockBluetoothAdapterWithAdvertisements>> |
| mock_bluetooth_adapter_; |
| std::unique_ptr<NiceMock<device::MockBluetoothDevice>> |
| mock_known_bluetooth_device_; |
| std::unique_ptr<NiceMock<device::MockBluetoothDevice>> |
| mock_unknown_bluetooth_device_; |
| scoped_refptr<NiceMock<device::MockBluetoothSocket>> mock_bluetooth_socket_; |
| std::unique_ptr<Adapter> adapter_; |
| |
| private: |
| base::test::TaskEnvironment task_environment_; |
| }; |
| |
| TEST_F(AdapterTest, TestRegisterAdvertisement_Success) { |
| RegisterAdvertisement(/*should_succeed=*/true, /*use_scan_data=*/false); |
| VerifyAdvertisement(); |
| } |
| |
| TEST_F(AdapterTest, TestRegisterAdvertisement_Error) { |
| RegisterAdvertisement(/*should_succeed=*/false, /*use_scan_data=*/false); |
| VerifyAdvertisement(); |
| } |
| |
| TEST_F(AdapterTest, TestRegisterAdvertisement_ScanResponseData) { |
| RegisterAdvertisement(/*should_succeed=*/true, /*use_scan_data=*/true); |
| VerifyAdvertisementWithScanData(); |
| } |
| |
| TEST_F(AdapterTest, TestConnectToServiceInsecurely_DisallowedUuid) { |
| // Do not call Adapter::AllowConnectionsForUuid(); |
| |
| base::RunLoop run_loop; |
| adapter_->ConnectToServiceInsecurely( |
| kKnownDeviceAddress, device::BluetoothUUID(kServiceId), |
| base::BindLambdaForTesting( |
| [&](mojom::ConnectToServiceResultPtr connect_to_service_result) { |
| EXPECT_FALSE(connect_to_service_result); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| |
| TEST_F(AdapterTest, TestConnectToServiceInsecurely_KnownDevice_Success) { |
| EXPECT_CALL( |
| *mock_known_bluetooth_device_, |
| ConnectToServiceInsecurely(device::BluetoothUUID(kServiceId), _, _)) |
| .WillOnce(RunOnceCallback<1>(mock_bluetooth_socket_)); |
| |
| adapter_->AllowConnectionsForUuid(device::BluetoothUUID(kServiceId)); |
| |
| base::RunLoop run_loop; |
| adapter_->ConnectToServiceInsecurely( |
| kKnownDeviceAddress, device::BluetoothUUID(kServiceId), |
| base::BindLambdaForTesting( |
| [&](mojom::ConnectToServiceResultPtr connect_to_service_result) { |
| EXPECT_TRUE(connect_to_service_result); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| |
| TEST_F(AdapterTest, TestConnectToServiceInsecurely_KnownDevice_Error) { |
| EXPECT_CALL( |
| *mock_known_bluetooth_device_, |
| ConnectToServiceInsecurely(device::BluetoothUUID(kServiceId), _, _)) |
| .WillOnce(RunOnceCallback<2>("Error")); |
| |
| adapter_->AllowConnectionsForUuid(device::BluetoothUUID(kServiceId)); |
| |
| base::RunLoop run_loop; |
| adapter_->ConnectToServiceInsecurely( |
| kKnownDeviceAddress, device::BluetoothUUID(kServiceId), |
| base::BindLambdaForTesting( |
| [&](mojom::ConnectToServiceResultPtr connect_to_service_result) { |
| EXPECT_FALSE(connect_to_service_result); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| |
| #if defined(OS_CHROMEOS) || defined(OS_LINUX) |
| TEST_F( |
| AdapterTest, |
| TestConnectToServiceInsecurely_UnknownDevice_Success_ServicesAlreadyResolved) { |
| EXPECT_CALL(*mock_bluetooth_adapter_, |
| ConnectDevice(kUnknownDeviceAddress, _, _, _)) |
| .WillOnce(RunOnceCallback<2>(mock_unknown_bluetooth_device_.get())); |
| EXPECT_CALL( |
| *mock_unknown_bluetooth_device_, |
| ConnectToServiceInsecurely(device::BluetoothUUID(kServiceId), _, _)) |
| .WillOnce(RunOnceCallback<1>(mock_bluetooth_socket_)); |
| EXPECT_CALL(*mock_unknown_bluetooth_device_, |
| IsGattServicesDiscoveryComplete()) |
| .WillOnce(Return(true)); |
| |
| adapter_->AllowConnectionsForUuid(device::BluetoothUUID(kServiceId)); |
| |
| base::RunLoop run_loop; |
| adapter_->ConnectToServiceInsecurely( |
| kUnknownDeviceAddress, device::BluetoothUUID(kServiceId), |
| base::BindLambdaForTesting( |
| [&](mojom::ConnectToServiceResultPtr connect_to_service_result) { |
| EXPECT_TRUE(connect_to_service_result); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| |
| TEST_F( |
| AdapterTest, |
| TestConnectToServiceInsecurely_UnknownDevice_Success_WaitForServicesToResolve) { |
| EXPECT_CALL(*mock_bluetooth_adapter_, |
| ConnectDevice(kUnknownDeviceAddress, _, _, _)) |
| .WillOnce(RunOnceCallback<2>(mock_unknown_bluetooth_device_.get())); |
| EXPECT_CALL( |
| *mock_unknown_bluetooth_device_, |
| ConnectToServiceInsecurely(device::BluetoothUUID(kServiceId), _, _)) |
| .WillOnce(RunOnceCallback<1>(mock_bluetooth_socket_)); |
| |
| // At first, return false to force |adapter_| to wait for the value to change, |
| // but subsequently return true. On that first call, post a task to trigger |
| // a notification that services are now resolved. |
| EXPECT_CALL(*mock_unknown_bluetooth_device_, |
| IsGattServicesDiscoveryComplete()) |
| .WillOnce(DoAll(InvokeWithoutArgs([this]() { |
| base::SequencedTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindLambdaForTesting([&]() { |
| adapter_->GattServicesDiscovered( |
| mock_bluetooth_adapter_.get(), |
| mock_unknown_bluetooth_device_.get()); |
| })); |
| }), |
| Return(false))) |
| .WillRepeatedly(Return(true)); |
| |
| adapter_->AllowConnectionsForUuid(device::BluetoothUUID(kServiceId)); |
| |
| base::RunLoop run_loop; |
| adapter_->ConnectToServiceInsecurely( |
| kUnknownDeviceAddress, device::BluetoothUUID(kServiceId), |
| base::BindLambdaForTesting( |
| [&](mojom::ConnectToServiceResultPtr connect_to_service_result) { |
| EXPECT_TRUE(connect_to_service_result); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| |
| TEST_F(AdapterTest, TestConnectToServiceInsecurely_UnknownDevice_Error) { |
| EXPECT_CALL(*mock_bluetooth_adapter_, |
| ConnectDevice(kUnknownDeviceAddress, _, _, _)) |
| .WillOnce(RunOnceCallback<3>()); |
| |
| adapter_->AllowConnectionsForUuid(device::BluetoothUUID(kServiceId)); |
| |
| base::RunLoop run_loop; |
| adapter_->ConnectToServiceInsecurely( |
| kUnknownDeviceAddress, device::BluetoothUUID(kServiceId), |
| base::BindLambdaForTesting( |
| [&](mojom::ConnectToServiceResultPtr connect_to_service_result) { |
| EXPECT_FALSE(connect_to_service_result); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| #else |
| TEST_F(AdapterTest, TestConnectToServiceInsecurely_UnknownDevice) { |
| adapter_->AllowConnectionsForUuid(device::BluetoothUUID(kServiceId)); |
| |
| base::RunLoop run_loop; |
| adapter_->ConnectToServiceInsecurely( |
| kUnknownDeviceAddress, device::BluetoothUUID(kServiceId), |
| base::BindLambdaForTesting( |
| [&](mojom::ConnectToServiceResultPtr connect_to_service_result) { |
| EXPECT_FALSE(connect_to_service_result); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| #endif |
| |
| TEST_F(AdapterTest, TestCreateRfcommServiceInsecurely_DisallowedUuid) { |
| // Do not call Adapter::AllowConnectionsForUuid(); |
| |
| base::RunLoop run_loop; |
| adapter_->CreateRfcommServiceInsecurely( |
| kServiceName, device::BluetoothUUID(kServiceId), |
| base::BindLambdaForTesting( |
| [&](mojo::PendingRemote<mojom::ServerSocket> pending_server_socket) { |
| EXPECT_FALSE(pending_server_socket); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| |
| TEST_F(AdapterTest, TestCreateRfcommServiceInsecurely_Error) { |
| EXPECT_CALL(*mock_bluetooth_adapter_, |
| CreateRfcommService(device::BluetoothUUID(kServiceId), _, _, _)) |
| .WillOnce(RunOnceCallback<3>("Error")); |
| |
| adapter_->AllowConnectionsForUuid(device::BluetoothUUID(kServiceId)); |
| |
| base::RunLoop run_loop; |
| adapter_->CreateRfcommServiceInsecurely( |
| kServiceName, device::BluetoothUUID(kServiceId), |
| base::BindLambdaForTesting( |
| [&](mojo::PendingRemote<mojom::ServerSocket> pending_server_socket) { |
| EXPECT_FALSE(pending_server_socket); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| |
| TEST_F(AdapterTest, TestCreateRfcommServiceInsecurely_Success) { |
| EXPECT_CALL(*mock_bluetooth_adapter_, |
| CreateRfcommService(device::BluetoothUUID(kServiceId), _, _, _)) |
| .WillOnce(RunOnceCallback<2>(mock_bluetooth_socket_)); |
| |
| adapter_->AllowConnectionsForUuid(device::BluetoothUUID(kServiceId)); |
| |
| base::RunLoop run_loop; |
| adapter_->CreateRfcommServiceInsecurely( |
| kServiceName, device::BluetoothUUID(kServiceId), |
| base::BindLambdaForTesting( |
| [&](mojo::PendingRemote<mojom::ServerSocket> pending_server_socket) { |
| EXPECT_TRUE(pending_server_socket); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| } |
| |
| } // namespace bluetooth |