| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/bluetooth/web_bluetooth_pairing_manager_impl.h" |
| |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/notreached.h" |
| #include "base/test/bind.h" |
| #include "base/test/task_environment.h" |
| #include "base/test/test_future.h" |
| #include "content/browser/bluetooth/web_bluetooth_pairing_manager.h" |
| #include "content/browser/bluetooth/web_bluetooth_pairing_manager_delegate.h" |
| #include "content/public/browser/bluetooth_delegate.h" |
| #include "device/bluetooth/test/mock_bluetooth_device.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| using ::base::test::SingleThreadTaskEnvironment; |
| using ::base::test::TestFuture; |
| using ::blink::WebBluetoothDeviceId; |
| using ::blink::mojom::WebBluetoothResult; |
| using ::blink::mojom::WebBluetoothService; |
| using ::blink::mojom::WebBluetoothWriteType; |
| using ::device::BluetoothDevice; |
| using ::device::MockBluetoothDevice; |
| using ::testing::Return; |
| using PairPromptResult = BluetoothDelegate::PairPromptResult; |
| |
| /** |
| * A collection of related Bluetooth test data. |
| */ |
| struct TestData { |
| const std::string characteristic_instance_id; |
| const std::string descriptor_instance_id; |
| const WebBluetoothDeviceId device_id; |
| const std::string device_address; |
| const std::string device_name; |
| const std::string pincode; |
| }; |
| |
| // Test instance data that are valid and are the ID's of the fake objects |
| // managed by these tests. |
| const TestData kValidTestData = { |
| .characteristic_instance_id = "valid-test-characteristic-id", |
| .descriptor_instance_id = "valid-test-descriptor-id", |
| .device_id = WebBluetoothDeviceId("000000000000000000000A=="), |
| .device_address = "00:11:22:33:44:55", |
| .device_name = "test device", |
| .pincode = "123456", |
| }; |
| |
| // All valid data, but does not represent any object managed by these tests. |
| const TestData kValidNonTestData = { |
| .characteristic_instance_id = "valid-non-test-characteristic-id", |
| .descriptor_instance_id = "valid-non-test-descriptor-id", |
| .device_id = WebBluetoothDeviceId("111111111111111111111A=="), |
| .device_address = "66:77:88:99:00:11", |
| .device_name = "non test device", |
| .pincode = "789012", |
| }; |
| |
| const TestData kInvalidTestData; |
| |
| } // namespace |
| |
| class BluetoothPairingManagerTest : public testing::Test, |
| public WebBluetoothPairingManagerDelegate { |
| public: |
| enum class AuthBehavior { |
| kSucceedFirst, // Successfully authenticate on first pair attempt. |
| kSucceedSecond, // Successfully authenticate on second pair attempt. |
| kFailAll, // Fail authentication on all pair attempts. |
| kSuspend, // Suspend in-progress authentication. |
| kFirstSuspend, // First auth is suspended, following will succeed. |
| kUnspecified, // Initial (error) behavior. |
| }; |
| |
| BluetoothPairingManagerTest() |
| : characteristic_value_{1, 2, 3, 5}, |
| descriptor_value_{4, 8, 15, 16, 23, 42}, |
| pairing_manager_( |
| std::make_unique<WebBluetoothPairingManagerImpl>(this)) {} |
| ~BluetoothPairingManagerTest() override = default; |
| |
| void SetUp() override {} |
| |
| WebBluetoothDeviceId GetCharacteristicDeviceID( |
| const std::string& characteristic_instance_id) override { |
| if (characteristic_instance_id == kValidTestData.characteristic_instance_id) |
| return kValidTestData.device_id; |
| return kInvalidTestData.device_id; |
| } |
| |
| WebBluetoothDeviceId GetDescriptorDeviceId( |
| const std::string& descriptor_instance_id) override { |
| if (descriptor_instance_id == kValidTestData.descriptor_instance_id) |
| return kValidTestData.device_id; |
| return kInvalidTestData.device_id; |
| } |
| |
| WebBluetoothDeviceId GetWebBluetoothDeviceId( |
| const std::string& device_address) override { |
| if (device_address == kValidTestData.device_address) |
| return kValidTestData.device_id; |
| return kInvalidTestData.device_id; |
| } |
| |
| void PairDevice(const WebBluetoothDeviceId& device_id, |
| BluetoothDevice::PairingDelegate* pairing_delegate, |
| BluetoothDevice::ConnectCallback callback) override { |
| ASSERT_TRUE(device_id.IsValid()); |
| EXPECT_EQ(device_id, kValidTestData.device_id); |
| num_pair_attempts_++; |
| |
| switch (auth_behavior_) { |
| case AuthBehavior::kSucceedFirst: |
| EXPECT_EQ(1, num_pair_attempts_); |
| device_paired_ = true; |
| std::move(callback).Run(/*error_code=*/std::nullopt); |
| break; |
| case AuthBehavior::kSucceedSecond: |
| switch (num_pair_attempts_) { |
| case 1: |
| std::move(callback).Run(BluetoothDevice::ERROR_AUTH_REJECTED); |
| break; |
| case 2: |
| device_paired_ = true; |
| std::move(callback).Run(/*error_code=*/std::nullopt); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| break; |
| case AuthBehavior::kFailAll: |
| std::move(callback).Run(BluetoothDevice::ERROR_AUTH_REJECTED); |
| break; |
| case AuthBehavior::kSuspend: |
| EXPECT_TRUE(pair_callback_.is_null()); |
| pair_callback_ = std::move(callback); |
| break; |
| case AuthBehavior::kFirstSuspend: |
| if (num_pair_attempts_ == 1) { |
| EXPECT_TRUE(pair_callback_.is_null()); |
| pair_callback_ = std::move(callback); |
| } else { |
| device_paired_ = true; |
| std::move(callback).Run(/*error_code=*/std::nullopt); |
| } |
| break; |
| case AuthBehavior::kUnspecified: |
| NOTREACHED() << "Test must set auth behavior"; |
| } |
| } |
| |
| void CancelPairing(const WebBluetoothDeviceId& device_id) override { |
| ASSERT_TRUE(device_id.IsValid()); |
| EXPECT_EQ(device_id, kValidTestData.device_id); |
| std::move(pair_callback_).Run(BluetoothDevice::ERROR_AUTH_CANCELED); |
| } |
| |
| void SetPinCode(const blink::WebBluetoothDeviceId& device_id, |
| const std::string& pincode) override { |
| ASSERT_TRUE(device_id.IsValid()); |
| EXPECT_EQ(device_id, kValidTestData.device_id); |
| std::move(pair_callback_).Run(/*error_code=*/std::nullopt); |
| } |
| |
| void PairConfirmed(const blink::WebBluetoothDeviceId& device_id) override { |
| ASSERT_TRUE(device_id.IsValid()); |
| EXPECT_EQ(device_id, kValidTestData.device_id); |
| std::move(pair_callback_).Run(/*error_code=*/std::nullopt); |
| } |
| |
| void ResumeSuspendedPairingWithSuccess() { |
| device_paired_ = true; |
| EXPECT_FALSE(pair_callback_.is_null()); |
| std::move(pair_callback_).Run(/*error_code=*/std::nullopt); |
| } |
| |
| void RemoteCharacteristicReadValue( |
| const std::string& characteristic_instance_id, |
| WebBluetoothService::RemoteCharacteristicReadValueCallback callback) |
| override { |
| if (characteristic_instance_id != |
| kValidTestData.characteristic_instance_id) { |
| std::move(callback).Run(WebBluetoothResult::CHARACTERISTIC_NOT_FOUND, |
| characteristic_value_); |
| return; |
| } |
| if (device_paired_) { |
| std::move(callback).Run(WebBluetoothResult::SUCCESS, |
| characteristic_value_); |
| return; |
| } |
| |
| std::move(callback).Run(WebBluetoothResult::CONNECT_AUTH_REJECTED, |
| /*value=*/{}); |
| } |
| |
| void RemoteDescriptorReadValue( |
| const std::string& descriptor_instance_id, |
| WebBluetoothService::RemoteDescriptorReadValueCallback callback) |
| override { |
| if (descriptor_instance_id != kValidTestData.descriptor_instance_id) { |
| std::move(callback).Run(WebBluetoothResult::DESCRIPTOR_NOT_FOUND, |
| descriptor_value_); |
| return; |
| } |
| if (device_paired_) { |
| std::move(callback).Run(WebBluetoothResult::SUCCESS, descriptor_value_); |
| return; |
| } |
| |
| std::move(callback).Run(WebBluetoothResult::CONNECT_AUTH_REJECTED, |
| /*value=*/{}); |
| } |
| |
| void RemoteCharacteristicWriteValue( |
| const std::string& characteristic_instance_id, |
| base::span<const uint8_t> value, |
| WebBluetoothWriteType write_type, |
| WebBluetoothService::RemoteCharacteristicWriteValueCallback callback) |
| override { |
| if (characteristic_instance_id != |
| kValidTestData.characteristic_instance_id) { |
| std::move(callback).Run(WebBluetoothResult::CHARACTERISTIC_NOT_FOUND); |
| return; |
| } |
| if (device_paired_) { |
| characteristic_value_ = std::vector<uint8_t>(value.begin(), value.end()); |
| std::move(callback).Run(WebBluetoothResult::SUCCESS); |
| return; |
| } |
| |
| std::move(callback).Run(WebBluetoothResult::CONNECT_AUTH_REJECTED); |
| } |
| |
| void RemoteDescriptorWriteValue( |
| const std::string& descriptor_instance_id, |
| base::span<const uint8_t> value, |
| WebBluetoothService::RemoteDescriptorWriteValueCallback callback) |
| override { |
| if (descriptor_instance_id != kValidTestData.descriptor_instance_id) { |
| std::move(callback).Run(WebBluetoothResult::DESCRIPTOR_NOT_FOUND); |
| return; |
| } |
| if (device_paired_) { |
| descriptor_value_ = std::vector<uint8_t>(value.begin(), value.end()); |
| std::move(callback).Run(WebBluetoothResult::SUCCESS); |
| return; |
| } |
| |
| std::move(callback).Run(WebBluetoothResult::CONNECT_AUTH_REJECTED); |
| } |
| |
| void RemoteCharacteristicStartNotificationsInternal( |
| const std::string& characteristic_instance_id, |
| mojo::AssociatedRemote<blink::mojom::WebBluetoothCharacteristicClient> |
| client, |
| blink::mojom::WebBluetoothService:: |
| RemoteCharacteristicStartNotificationsCallback callback) override { |
| if (characteristic_instance_id != |
| kValidTestData.characteristic_instance_id) { |
| std::move(callback).Run(WebBluetoothResult::CHARACTERISTIC_NOT_FOUND); |
| return; |
| } |
| if (device_paired_) { |
| std::move(callback).Run(WebBluetoothResult::SUCCESS); |
| return; |
| } |
| |
| std::move(callback).Run(WebBluetoothResult::CONNECT_AUTH_REJECTED); |
| } |
| |
| void PromptForBluetoothPairing( |
| const std::u16string& device_identifier, |
| BluetoothDelegate::PairPromptCallback callback, |
| BluetoothDelegate::PairingKind pairing_kind, |
| const std::optional<std::u16string>& pin) override { |
| std::move(callback).Run(prompt_result_); |
| } |
| |
| void SetAuthBehavior(AuthBehavior auth_behavior) { |
| auth_behavior_ = auth_behavior; |
| } |
| |
| int num_pair_attempts() const { return num_pair_attempts_; } |
| |
| void SetPromptResult(PairPromptResult result) { |
| prompt_result_ = result; |
| } |
| |
| const std::vector<uint8_t>& characteristic_value() const { |
| return characteristic_value_; |
| } |
| |
| const std::vector<uint8_t>& descriptor_value() const { |
| return descriptor_value_; |
| } |
| |
| void DeletePairingManager() { pairing_manager_.reset(); } |
| |
| WebBluetoothPairingManagerImpl* pairing_manager() { |
| return pairing_manager_.get(); |
| } |
| |
| protected: |
| BluetoothDevice::ConnectCallback pair_callback_; |
| |
| private: |
| std::vector<uint8_t> characteristic_value_; |
| std::vector<uint8_t> descriptor_value_; |
| int num_pair_attempts_ = 0; |
| bool device_paired_ = false; |
| AuthBehavior auth_behavior_ = AuthBehavior::kUnspecified; |
| PairPromptResult prompt_result_; |
| std::unique_ptr<WebBluetoothPairingManagerImpl> pairing_manager_; |
| SingleThreadTaskEnvironment single_threaded_task_environment_; |
| }; |
| |
| TEST_F(BluetoothPairingManagerTest, ReadSuccessfulAuthFirstSuccess) { |
| SetAuthBehavior(AuthBehavior::kSucceedFirst); |
| |
| const std::vector<uint8_t> expected_value = characteristic_value(); |
| base::RunLoop loop; |
| pairing_manager()->PairForCharacteristicReadValue( |
| kValidTestData.characteristic_instance_id, |
| base::BindLambdaForTesting( |
| [&loop, &expected_value](WebBluetoothResult result, |
| base::span<const uint8_t> value) { |
| EXPECT_EQ(WebBluetoothResult::SUCCESS, result); |
| EXPECT_EQ(value, expected_value) |
| << "Incorrect characteristic value"; |
| loop.Quit(); |
| })); |
| |
| loop.Run(); |
| EXPECT_EQ(1, num_pair_attempts()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, ReadSuccessfulAuthSecondSuccess) { |
| SetAuthBehavior(AuthBehavior::kSucceedSecond); |
| |
| const std::vector<uint8_t> expected_value = characteristic_value(); |
| base::RunLoop loop; |
| pairing_manager()->PairForCharacteristicReadValue( |
| kValidTestData.characteristic_instance_id, |
| base::BindLambdaForTesting( |
| [&loop, &expected_value](WebBluetoothResult result, |
| base::span<const uint8_t> value) { |
| EXPECT_EQ(WebBluetoothResult::SUCCESS, result); |
| EXPECT_EQ(value, expected_value) |
| << "Incorrect characteristic value"; |
| loop.Quit(); |
| })); |
| |
| loop.Run(); |
| EXPECT_EQ(2, num_pair_attempts()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, ReadFailAllAuthsFail) { |
| SetAuthBehavior(AuthBehavior::kFailAll); |
| |
| base::RunLoop loop; |
| pairing_manager()->PairForCharacteristicReadValue( |
| kValidTestData.characteristic_instance_id, |
| base::BindLambdaForTesting( |
| [&loop](WebBluetoothResult result, base::span<const uint8_t> value) { |
| EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_REJECTED, result); |
| EXPECT_TRUE(value.empty()); |
| loop.Quit(); |
| })); |
| |
| loop.Run(); |
| EXPECT_EQ(WebBluetoothPairingManagerImpl::kMaxPairAttempts, |
| num_pair_attempts()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, ReadInvalidCharacteristicId) { |
| SetAuthBehavior(AuthBehavior::kFailAll); |
| |
| base::RunLoop loop; |
| pairing_manager()->PairForCharacteristicReadValue( |
| kValidNonTestData.characteristic_instance_id, |
| base::BindLambdaForTesting( |
| [&loop](WebBluetoothResult result, base::span<const uint8_t> value) { |
| EXPECT_EQ(WebBluetoothResult::CONNECT_UNKNOWN_ERROR, result); |
| EXPECT_TRUE(value.empty()); |
| loop.Quit(); |
| })); |
| |
| loop.Run(); |
| EXPECT_EQ(0, num_pair_attempts()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, ReadCharacteristicDeleteDelegate) { |
| SetAuthBehavior(AuthBehavior::kSuspend); |
| |
| base::RunLoop loop; |
| pairing_manager()->PairForCharacteristicReadValue( |
| kValidTestData.characteristic_instance_id, |
| base::BindLambdaForTesting( |
| [&loop](WebBluetoothResult result, base::span<const uint8_t> value) { |
| EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_CANCELED, result); |
| EXPECT_TRUE(value.empty()); |
| loop.Quit(); |
| })); |
| |
| // Deleting the pairing manager will cancel all pending device pairing |
| // operations. Test this is true. |
| DeletePairingManager(); |
| loop.Run(); |
| EXPECT_EQ(1, num_pair_attempts()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, ReadCharacteristicDoublePair) { |
| SetAuthBehavior(AuthBehavior::kFirstSuspend); |
| |
| int callback_count = 0; |
| base::RunLoop loop; |
| const std::vector<uint8_t> expected_value = characteristic_value(); |
| pairing_manager()->PairForCharacteristicReadValue( |
| kValidTestData.characteristic_instance_id, |
| base::BindLambdaForTesting( |
| [&loop, &callback_count, &expected_value]( |
| WebBluetoothResult result, base::span<const uint8_t> value) { |
| EXPECT_EQ(WebBluetoothResult::SUCCESS, result); |
| EXPECT_EQ(value, expected_value); |
| if (++callback_count == 2) |
| loop.Quit(); |
| })); |
| |
| // Now try to pair for reading a second time. This should fail due to an |
| // in-progress pairing. |
| pairing_manager()->PairForCharacteristicReadValue( |
| kValidTestData.characteristic_instance_id, |
| base::BindLambdaForTesting( |
| [&loop, &callback_count](WebBluetoothResult result, |
| base::span<const uint8_t> value) { |
| EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_CANCELED, result); |
| if (++callback_count == 2) |
| loop.Quit(); |
| })); |
| |
| ResumeSuspendedPairingWithSuccess(); |
| loop.Run(); |
| |
| EXPECT_EQ(1, num_pair_attempts()) << "Only the first operation should pair"; |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, |
| WriteCharacteristicSuccessfulAuthFirstSuccess) { |
| SetAuthBehavior(AuthBehavior::kSucceedFirst); |
| |
| const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11}; |
| EXPECT_NE(kWriteValue, characteristic_value()); |
| |
| base::RunLoop loop; |
| pairing_manager()->PairForCharacteristicWriteValue( |
| kValidTestData.characteristic_instance_id, kWriteValue, |
| WebBluetoothWriteType::kWriteWithResponse, |
| base::BindLambdaForTesting([&loop](WebBluetoothResult result) { |
| EXPECT_EQ(WebBluetoothResult::SUCCESS, result); |
| loop.Quit(); |
| })); |
| |
| loop.Run(); |
| EXPECT_EQ(1, num_pair_attempts()); |
| EXPECT_EQ(kWriteValue, characteristic_value()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, |
| WriteCharacteristicSuccessfulAuthSecondSuccess) { |
| SetAuthBehavior(AuthBehavior::kSucceedSecond); |
| |
| const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11}; |
| EXPECT_NE(kWriteValue, characteristic_value()); |
| |
| base::RunLoop loop; |
| pairing_manager()->PairForCharacteristicWriteValue( |
| kValidTestData.characteristic_instance_id, kWriteValue, |
| WebBluetoothWriteType::kWriteWithResponse, |
| base::BindLambdaForTesting([&loop](WebBluetoothResult result) { |
| EXPECT_EQ(WebBluetoothResult::SUCCESS, result); |
| loop.Quit(); |
| })); |
| |
| loop.Run(); |
| EXPECT_EQ(2, num_pair_attempts()); |
| EXPECT_EQ(kWriteValue, characteristic_value()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, WriteCharacteristicFailAllAuthsFail) { |
| SetAuthBehavior(AuthBehavior::kFailAll); |
| |
| const std::vector<uint8_t> default_test_characteristic_value = |
| characteristic_value(); |
| |
| const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11}; |
| EXPECT_NE(kWriteValue, default_test_characteristic_value); |
| |
| base::RunLoop loop; |
| pairing_manager()->PairForCharacteristicWriteValue( |
| kValidTestData.characteristic_instance_id, kWriteValue, |
| WebBluetoothWriteType::kWriteWithResponse, |
| base::BindLambdaForTesting([&loop](WebBluetoothResult result) { |
| EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_REJECTED, result); |
| loop.Quit(); |
| })); |
| |
| loop.Run(); |
| EXPECT_EQ(WebBluetoothPairingManagerImpl::kMaxPairAttempts, |
| num_pair_attempts()); |
| EXPECT_EQ(default_test_characteristic_value, characteristic_value()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, WriteCharacteristicInvalidID) { |
| SetAuthBehavior(AuthBehavior::kFailAll); |
| |
| const std::vector<uint8_t> default_test_characteristic_value = |
| characteristic_value(); |
| |
| const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11}; |
| EXPECT_NE(kWriteValue, default_test_characteristic_value); |
| |
| base::RunLoop loop; |
| pairing_manager()->PairForCharacteristicWriteValue( |
| kValidNonTestData.characteristic_instance_id, kWriteValue, |
| WebBluetoothWriteType::kWriteWithResponse, |
| base::BindLambdaForTesting([&loop](WebBluetoothResult result) { |
| EXPECT_EQ(WebBluetoothResult::CONNECT_UNKNOWN_ERROR, result); |
| loop.Quit(); |
| })); |
| |
| loop.Run(); |
| EXPECT_EQ(0, num_pair_attempts()); |
| EXPECT_EQ(default_test_characteristic_value, characteristic_value()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, WriteCharacteristicDeleteDelegate) { |
| SetAuthBehavior(AuthBehavior::kSuspend); |
| |
| const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11}; |
| base::RunLoop loop; |
| pairing_manager()->PairForCharacteristicWriteValue( |
| kValidTestData.characteristic_instance_id, kWriteValue, |
| WebBluetoothWriteType::kWriteWithResponse, |
| base::BindLambdaForTesting([&loop](WebBluetoothResult result) { |
| EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_CANCELED, result); |
| loop.Quit(); |
| })); |
| |
| // Verify that deleting the pairing manager will cancel all pending device |
| // pairing. |
| DeletePairingManager(); |
| loop.Run(); |
| EXPECT_EQ(1, num_pair_attempts()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, WriteCharacteristicDoublePair) { |
| SetAuthBehavior(AuthBehavior::kFirstSuspend); |
| |
| const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11}; |
| int callback_count = 0; |
| base::RunLoop loop; |
| pairing_manager()->PairForCharacteristicWriteValue( |
| kValidTestData.characteristic_instance_id, kWriteValue, |
| WebBluetoothWriteType::kWriteWithResponse, |
| base::BindLambdaForTesting( |
| [&loop, &callback_count](WebBluetoothResult result) { |
| EXPECT_EQ(WebBluetoothResult::SUCCESS, result); |
| if (++callback_count == 2) |
| loop.Quit(); |
| })); |
| |
| // Now try to pair for writing a second time. This should fail due to an |
| // in-progress pairing. |
| pairing_manager()->PairForCharacteristicWriteValue( |
| kValidTestData.characteristic_instance_id, kWriteValue, |
| WebBluetoothWriteType::kWriteWithResponse, |
| base::BindLambdaForTesting( |
| [&loop, &callback_count](WebBluetoothResult result) { |
| EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_CANCELED, result); |
| if (++callback_count == 2) |
| loop.Quit(); |
| })); |
| |
| ResumeSuspendedPairingWithSuccess(); |
| loop.Run(); |
| |
| EXPECT_EQ(1, num_pair_attempts()) << "Only the first operation should pair"; |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, DescriptorReadSuccessfulAuthFirstSuccess) { |
| SetAuthBehavior(AuthBehavior::kSucceedFirst); |
| |
| const std::vector<uint8_t> expected_value = descriptor_value(); |
| base::RunLoop loop; |
| pairing_manager()->PairForDescriptorReadValue( |
| kValidTestData.descriptor_instance_id, |
| base::BindLambdaForTesting( |
| [&loop, &expected_value](WebBluetoothResult result, |
| base::span<const uint8_t> value) { |
| EXPECT_EQ(WebBluetoothResult::SUCCESS, result); |
| EXPECT_EQ(value, expected_value) << "Incorrect descriptor value"; |
| loop.Quit(); |
| })); |
| |
| loop.Run(); |
| EXPECT_EQ(1, num_pair_attempts()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, DescriptorReadSuccessfulAuthSecondSuccess) { |
| SetAuthBehavior(AuthBehavior::kSucceedSecond); |
| |
| const std::vector<uint8_t> expected_value = descriptor_value(); |
| base::RunLoop loop; |
| pairing_manager()->PairForDescriptorReadValue( |
| kValidTestData.descriptor_instance_id, |
| base::BindLambdaForTesting( |
| [&loop, &expected_value](WebBluetoothResult result, |
| base::span<const uint8_t> value) { |
| EXPECT_EQ(WebBluetoothResult::SUCCESS, result); |
| EXPECT_EQ(value, expected_value) << "Incorrect descriptor value"; |
| loop.Quit(); |
| })); |
| |
| loop.Run(); |
| EXPECT_EQ(2, num_pair_attempts()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, DescriptorReadFailAllAuthsFail) { |
| SetAuthBehavior(AuthBehavior::kFailAll); |
| |
| base::RunLoop loop; |
| pairing_manager()->PairForDescriptorReadValue( |
| kValidTestData.descriptor_instance_id, |
| base::BindLambdaForTesting( |
| [&loop](WebBluetoothResult result, base::span<const uint8_t> value) { |
| EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_REJECTED, result); |
| EXPECT_TRUE(value.empty()); |
| loop.Quit(); |
| })); |
| |
| loop.Run(); |
| EXPECT_EQ(WebBluetoothPairingManagerImpl::kMaxPairAttempts, |
| num_pair_attempts()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, DescriptorReadInvalidDescriptorId) { |
| SetAuthBehavior(AuthBehavior::kFailAll); |
| |
| base::RunLoop loop; |
| pairing_manager()->PairForDescriptorReadValue( |
| kValidNonTestData.descriptor_instance_id, |
| base::BindLambdaForTesting( |
| [&loop](WebBluetoothResult result, base::span<const uint8_t> value) { |
| EXPECT_EQ(WebBluetoothResult::CONNECT_UNKNOWN_ERROR, result); |
| loop.Quit(); |
| })); |
| |
| loop.Run(); |
| EXPECT_EQ(0, num_pair_attempts()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, ReadDescriptorDeleteDelegate) { |
| SetAuthBehavior(AuthBehavior::kSuspend); |
| |
| base::RunLoop loop; |
| pairing_manager()->PairForDescriptorReadValue( |
| kValidTestData.descriptor_instance_id, |
| base::BindLambdaForTesting( |
| [&loop](WebBluetoothResult result, base::span<const uint8_t> value) { |
| EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_CANCELED, result); |
| loop.Quit(); |
| })); |
| |
| // Verify that deleting the pairing manager will cancel all pending device |
| // pairing. |
| DeletePairingManager(); |
| loop.Run(); |
| EXPECT_EQ(1, num_pair_attempts()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, ReadDescriptorDoublePair) { |
| SetAuthBehavior(AuthBehavior::kFirstSuspend); |
| |
| const std::vector<uint8_t> expected_value = descriptor_value(); |
| int callback_count = 0; |
| base::RunLoop loop; |
| pairing_manager()->PairForDescriptorReadValue( |
| kValidTestData.descriptor_instance_id, |
| base::BindLambdaForTesting( |
| [&loop, &callback_count, &expected_value]( |
| WebBluetoothResult result, base::span<const uint8_t> value) { |
| EXPECT_EQ(WebBluetoothResult::SUCCESS, result); |
| EXPECT_EQ(value, expected_value) << "Incorrect descriptor value"; |
| if (++callback_count == 2) |
| loop.Quit(); |
| })); |
| |
| // Now try to pair for reading a second time. This should fail due to an |
| // in-progress pairing. |
| pairing_manager()->PairForDescriptorReadValue( |
| kValidTestData.descriptor_instance_id, |
| base::BindLambdaForTesting( |
| [&loop, &callback_count](WebBluetoothResult result, |
| base::span<const uint8_t> value) { |
| EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_CANCELED, result); |
| EXPECT_TRUE(value.empty()); |
| if (++callback_count == 2) |
| loop.Quit(); |
| })); |
| |
| ResumeSuspendedPairingWithSuccess(); |
| loop.Run(); |
| |
| EXPECT_EQ(1, num_pair_attempts()) << "Only the first operation should pair"; |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, WriteDescriptorSuccessfulAuthFirstSuccess) { |
| SetAuthBehavior(AuthBehavior::kSucceedFirst); |
| |
| const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11}; |
| EXPECT_NE(kWriteValue, descriptor_value()); |
| |
| base::RunLoop loop; |
| pairing_manager()->PairForDescriptorWriteValue( |
| kValidTestData.descriptor_instance_id, kWriteValue, |
| base::BindLambdaForTesting([&loop](WebBluetoothResult result) { |
| EXPECT_EQ(WebBluetoothResult::SUCCESS, result); |
| loop.Quit(); |
| })); |
| |
| loop.Run(); |
| EXPECT_EQ(1, num_pair_attempts()); |
| EXPECT_EQ(kWriteValue, descriptor_value()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, |
| WriteDescriptorSuccessfulAuthSecondSuccess) { |
| SetAuthBehavior(AuthBehavior::kSucceedSecond); |
| |
| const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11}; |
| EXPECT_NE(kWriteValue, descriptor_value()); |
| |
| base::RunLoop loop; |
| pairing_manager()->PairForDescriptorWriteValue( |
| kValidTestData.descriptor_instance_id, kWriteValue, |
| base::BindLambdaForTesting([&loop](WebBluetoothResult result) { |
| EXPECT_EQ(WebBluetoothResult::SUCCESS, result); |
| loop.Quit(); |
| })); |
| |
| loop.Run(); |
| EXPECT_EQ(2, num_pair_attempts()); |
| EXPECT_EQ(kWriteValue, descriptor_value()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, WriteDescriptorFailAllAuthsFail) { |
| SetAuthBehavior(AuthBehavior::kFailAll); |
| |
| const std::vector<uint8_t> default_test_descriptor_value = descriptor_value(); |
| |
| const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11}; |
| EXPECT_NE(kWriteValue, default_test_descriptor_value); |
| |
| base::RunLoop loop; |
| pairing_manager()->PairForDescriptorWriteValue( |
| kValidTestData.descriptor_instance_id, kWriteValue, |
| base::BindLambdaForTesting([&loop](WebBluetoothResult result) { |
| EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_REJECTED, result); |
| loop.Quit(); |
| })); |
| |
| loop.Run(); |
| EXPECT_EQ(WebBluetoothPairingManagerImpl::kMaxPairAttempts, |
| num_pair_attempts()); |
| EXPECT_EQ(default_test_descriptor_value, descriptor_value()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, WriteDescriptorInvalidID) { |
| SetAuthBehavior(AuthBehavior::kFailAll); |
| |
| const std::vector<uint8_t> default_test_descriptor_value = descriptor_value(); |
| |
| const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11}; |
| EXPECT_NE(kWriteValue, default_test_descriptor_value); |
| |
| base::RunLoop loop; |
| pairing_manager()->PairForDescriptorWriteValue( |
| kValidNonTestData.descriptor_instance_id, kWriteValue, |
| base::BindLambdaForTesting([&loop](WebBluetoothResult result) { |
| EXPECT_EQ(WebBluetoothResult::CONNECT_UNKNOWN_ERROR, result); |
| loop.Quit(); |
| })); |
| |
| loop.Run(); |
| EXPECT_EQ(0, num_pair_attempts()); |
| EXPECT_EQ(default_test_descriptor_value, descriptor_value()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, WriteDescriptorDeleteDelegate) { |
| SetAuthBehavior(AuthBehavior::kSuspend); |
| |
| const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11}; |
| base::RunLoop loop; |
| pairing_manager()->PairForDescriptorWriteValue( |
| kValidTestData.descriptor_instance_id, kWriteValue, |
| base::BindLambdaForTesting([&loop](WebBluetoothResult result) { |
| EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_CANCELED, result); |
| loop.Quit(); |
| })); |
| |
| // Verify that deleting the pairing manager will cancel all pending device |
| // pairing. |
| DeletePairingManager(); |
| loop.Run(); |
| EXPECT_EQ(1, num_pair_attempts()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, WriteDescriptorDoublePair) { |
| SetAuthBehavior(AuthBehavior::kFirstSuspend); |
| |
| const std::vector<uint8_t> kWriteValue = {8, 9, 10, 11}; |
| int callback_count = 0; |
| base::RunLoop loop; |
| pairing_manager()->PairForDescriptorWriteValue( |
| kValidTestData.descriptor_instance_id, kWriteValue, |
| base::BindLambdaForTesting( |
| [&loop, &callback_count](WebBluetoothResult result) { |
| EXPECT_EQ(WebBluetoothResult::SUCCESS, result); |
| if (++callback_count == 2) |
| loop.Quit(); |
| })); |
| |
| // Now try to pair for writing a second time. This should fail due to an |
| // in-progress pairing. |
| pairing_manager()->PairForDescriptorWriteValue( |
| kValidTestData.descriptor_instance_id, kWriteValue, |
| base::BindLambdaForTesting( |
| [&loop, &callback_count](WebBluetoothResult result) { |
| EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_CANCELED, result); |
| if (++callback_count == 2) |
| loop.Quit(); |
| })); |
| |
| ResumeSuspendedPairingWithSuccess(); |
| loop.Run(); |
| |
| EXPECT_EQ(1, num_pair_attempts()) << "Only the first operation should pair"; |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, CredentialPromptPINSuccess) { |
| BluetoothDelegate::PairPromptResult result; |
| result.result_code = BluetoothDelegate::PairPromptStatus::kSuccess; |
| result.pin = kValidTestData.pincode; |
| SetPromptResult(result); |
| |
| MockBluetoothDevice device(/*adapter=*/nullptr, |
| /*bluetooth_class=*/0, |
| kValidTestData.device_name.c_str(), |
| kValidTestData.device_address, |
| /*initially_paired=*/false, |
| /*connected=*/true); |
| |
| EXPECT_CALL(device, GetAddress()); |
| EXPECT_CALL(device, GetNameForDisplay()); |
| |
| base::RunLoop run_loop; |
| pair_callback_ = base::BindLambdaForTesting( |
| [&run_loop](std::optional<BluetoothDevice::ConnectErrorCode> error_code) { |
| EXPECT_FALSE(error_code); |
| run_loop.Quit(); |
| }); |
| pairing_manager()->RequestPinCode(&device); |
| run_loop.Run(); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, CredentialPromptPINCancelled) { |
| PairPromptResult result; |
| result.result_code = BluetoothDelegate::PairPromptStatus::kCancelled; |
| SetPromptResult(result); |
| |
| MockBluetoothDevice device(/*adapter=*/nullptr, |
| /*bluetooth_class=*/0, |
| kValidTestData.device_name.c_str(), |
| kValidTestData.device_address, |
| /*initially_paired=*/false, |
| /*connected=*/true); |
| |
| EXPECT_CALL(device, GetAddress()); |
| EXPECT_CALL(device, GetNameForDisplay()); |
| |
| base::RunLoop run_loop; |
| pair_callback_ = base::BindLambdaForTesting( |
| [&run_loop](std::optional<BluetoothDevice::ConnectErrorCode> error_code) { |
| EXPECT_EQ(BluetoothDevice::ERROR_AUTH_CANCELED, *error_code); |
| run_loop.Quit(); |
| }); |
| pairing_manager()->RequestPinCode(&device); |
| run_loop.Run(); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, CredentialPromptPasskeyCancelled) { |
| MockBluetoothDevice device(/*adapter=*/nullptr, |
| /*bluetooth_class=*/0, |
| kValidTestData.device_name.c_str(), |
| kValidTestData.device_address, |
| /*initially_paired=*/false, |
| /*connected=*/true); |
| |
| EXPECT_CALL(device, CancelPairing()); |
| |
| // Passkey not supported. Verify pairing cancelled. |
| pairing_manager()->RequestPasskey(&device); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, PairConfirmPromptSuccess) { |
| PairPromptResult result; |
| result.result_code = BluetoothDelegate::PairPromptStatus::kSuccess; |
| SetPromptResult(result); |
| |
| MockBluetoothDevice device(/*adapter=*/nullptr, |
| /*bluetooth_class=*/0, |
| kValidTestData.device_name.c_str(), |
| kValidTestData.device_address, |
| /*initially_paired=*/false, |
| /*connected=*/true); |
| |
| EXPECT_CALL(device, GetAddress()); |
| EXPECT_CALL(device, GetNameForDisplay()); |
| |
| TestFuture<std::optional<BluetoothDevice::ConnectErrorCode>> future; |
| pair_callback_ = future.GetCallback(); |
| pairing_manager()->AuthorizePairing(&device); |
| EXPECT_FALSE(future.Get()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, PairConfirmPromptCancelled) { |
| PairPromptResult result; |
| result.result_code = BluetoothDelegate::PairPromptStatus::kCancelled; |
| SetPromptResult(result); |
| |
| MockBluetoothDevice device(/*adapter=*/nullptr, |
| /*bluetooth_class=*/0, |
| kValidTestData.device_name.c_str(), |
| kValidTestData.device_address, |
| /*initially_paired=*/false, |
| /*connected=*/true); |
| |
| EXPECT_CALL(device, GetAddress()); |
| EXPECT_CALL(device, GetNameForDisplay()); |
| |
| TestFuture<std::optional<BluetoothDevice::ConnectErrorCode>> future; |
| pair_callback_ = future.GetCallback(); |
| pairing_manager()->AuthorizePairing(&device); |
| EXPECT_EQ(BluetoothDevice::ERROR_AUTH_CANCELED, future.Get()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, PairConfirmPinPromptSuccess) { |
| PairPromptResult result; |
| result.result_code = BluetoothDelegate::PairPromptStatus::kSuccess; |
| SetPromptResult(result); |
| |
| MockBluetoothDevice device(/*adapter=*/nullptr, |
| /*bluetooth_class=*/0, |
| kValidTestData.device_name.c_str(), |
| kValidTestData.device_address, |
| /*initially_paired=*/false, |
| /*connected=*/true); |
| |
| EXPECT_CALL(device, GetAddress()); |
| EXPECT_CALL(device, GetNameForDisplay()); |
| |
| TestFuture<std::optional<BluetoothDevice::ConnectErrorCode>> future; |
| pair_callback_ = future.GetCallback(); |
| pairing_manager()->ConfirmPasskey(&device, 123456); |
| EXPECT_FALSE(future.Get()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, PairConfirmPinPromptCancelled) { |
| PairPromptResult result; |
| result.result_code = BluetoothDelegate::PairPromptStatus::kCancelled; |
| SetPromptResult(result); |
| |
| MockBluetoothDevice device(/*adapter=*/nullptr, |
| /*bluetooth_class=*/0, |
| kValidTestData.device_name.c_str(), |
| kValidTestData.device_address, |
| /*initially_paired=*/false, |
| /*connected=*/true); |
| |
| EXPECT_CALL(device, GetAddress()); |
| EXPECT_CALL(device, GetNameForDisplay()); |
| |
| TestFuture<std::optional<BluetoothDevice::ConnectErrorCode>> future; |
| pair_callback_ = future.GetCallback(); |
| pairing_manager()->ConfirmPasskey(&device, 123456); |
| EXPECT_EQ(BluetoothDevice::ERROR_AUTH_CANCELED, future.Get()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, StartNotificationsAllAuthsSuccess) { |
| SetAuthBehavior(AuthBehavior::kSucceedFirst); |
| |
| base::RunLoop loop; |
| mojo::AssociatedRemote<blink::mojom::WebBluetoothCharacteristicClient> client; |
| pairing_manager()->PairForCharacteristicStartNotifications( |
| kValidTestData.characteristic_instance_id, std::move(client), |
| base::BindLambdaForTesting([&loop](WebBluetoothResult result) { |
| EXPECT_EQ(WebBluetoothResult::SUCCESS, result); |
| loop.Quit(); |
| })); |
| |
| loop.Run(); |
| EXPECT_EQ(1, num_pair_attempts()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, StartNotificationsAuthSecondSuccess) { |
| SetAuthBehavior(AuthBehavior::kSucceedSecond); |
| |
| base::RunLoop loop; |
| mojo::AssociatedRemote<blink::mojom::WebBluetoothCharacteristicClient> client; |
| pairing_manager()->PairForCharacteristicStartNotifications( |
| kValidTestData.characteristic_instance_id, std::move(client), |
| base::BindLambdaForTesting([&loop](WebBluetoothResult result) { |
| EXPECT_EQ(WebBluetoothResult::SUCCESS, result); |
| loop.Quit(); |
| })); |
| |
| loop.Run(); |
| EXPECT_EQ(2, num_pair_attempts()); |
| } |
| |
| TEST_F(BluetoothPairingManagerTest, StartNotificationsAllAuthsFail) { |
| SetAuthBehavior(AuthBehavior::kFailAll); |
| |
| base::RunLoop loop; |
| mojo::AssociatedRemote<blink::mojom::WebBluetoothCharacteristicClient> client; |
| pairing_manager()->PairForCharacteristicStartNotifications( |
| kValidTestData.characteristic_instance_id, std::move(client), |
| base::BindLambdaForTesting([&loop](WebBluetoothResult result) { |
| EXPECT_EQ(WebBluetoothResult::CONNECT_AUTH_REJECTED, result); |
| loop.Quit(); |
| })); |
| |
| loop.Run(); |
| EXPECT_EQ(WebBluetoothPairingManagerImpl::kMaxPairAttempts, |
| num_pair_attempts()); |
| } |
| |
| } // namespace content |