| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromeos/components/tether/ble_advertiser_impl.h" |
| |
| #include "base/bind.h" |
| #include "base/callback_forward.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/stl_util.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "base/test/test_simple_task_runner.h" |
| #include "chromeos/services/secure_channel/ble_constants.h" |
| #include "chromeos/services/secure_channel/device_id_pair.h" |
| #include "chromeos/services/secure_channel/error_tolerant_ble_advertisement_impl.h" |
| #include "chromeos/services/secure_channel/fake_ble_service_data_helper.h" |
| #include "chromeos/services/secure_channel/fake_ble_synchronizer.h" |
| #include "chromeos/services/secure_channel/fake_error_tolerant_ble_advertisement.h" |
| #include "components/cryptauth/proto/cryptauth_api.pb.h" |
| #include "components/cryptauth/remote_device_test_util.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace chromeos { |
| |
| namespace tether { |
| |
| namespace { |
| |
| const char kStubLocalDeviceId[] = "N/A"; |
| |
| std::vector<cryptauth::DataWithTimestamp> GenerateFakeAdvertisements() { |
| cryptauth::DataWithTimestamp advertisement1("advertisement1", 1000L, 2000L); |
| cryptauth::DataWithTimestamp advertisement2("advertisement2", 2000L, 3000L); |
| cryptauth::DataWithTimestamp advertisement3("advertisement3", 3000L, 4000L); |
| |
| std::vector<cryptauth::DataWithTimestamp> advertisements = { |
| advertisement1, advertisement2, advertisement3}; |
| return advertisements; |
| } |
| |
| class FakeErrorTolerantBleAdvertisementFactory |
| : public secure_channel::ErrorTolerantBleAdvertisementImpl::Factory { |
| public: |
| FakeErrorTolerantBleAdvertisementFactory() = default; |
| ~FakeErrorTolerantBleAdvertisementFactory() override = default; |
| |
| const std::vector<secure_channel::FakeErrorTolerantBleAdvertisement*>& |
| active_advertisements() { |
| return active_advertisements_; |
| } |
| |
| size_t num_created() { return num_created_; } |
| |
| // secure_channel::ErrorTolerantBleAdvertisementImpl::Factory: |
| std::unique_ptr<secure_channel::ErrorTolerantBleAdvertisement> BuildInstance( |
| const secure_channel::DeviceIdPair& device_id_pair, |
| std::unique_ptr<cryptauth::DataWithTimestamp> advertisement_data, |
| secure_channel::BleSynchronizerBase* ble_synchronizer) override { |
| auto fake_advertisement = |
| std::make_unique<secure_channel::FakeErrorTolerantBleAdvertisement>( |
| device_id_pair, |
| base::BindOnce(&FakeErrorTolerantBleAdvertisementFactory:: |
| OnFakeAdvertisementDeleted, |
| base::Unretained(this))); |
| active_advertisements_.push_back(fake_advertisement.get()); |
| ++num_created_; |
| return fake_advertisement; |
| } |
| |
| protected: |
| void OnFakeAdvertisementDeleted( |
| const secure_channel::DeviceIdPair& device_id_pair) { |
| auto it = std::find_if( |
| active_advertisements_.begin(), active_advertisements_.end(), |
| [&device_id_pair](const auto* advertisement) { |
| return advertisement->device_id_pair() == device_id_pair; |
| }); |
| active_advertisements_.erase(it); |
| } |
| |
| private: |
| std::vector<secure_channel::FakeErrorTolerantBleAdvertisement*> |
| active_advertisements_; |
| size_t num_created_ = 0; |
| }; |
| |
| class TestObserver final : public BleAdvertiser::Observer { |
| public: |
| TestObserver() = default; |
| ~TestObserver() override = default; |
| |
| size_t num_times_all_advertisements_unregistered() { |
| return num_times_all_advertisements_unregistered_; |
| } |
| |
| // BleAdvertiser::Observer: |
| void OnAllAdvertisementsUnregistered() override { |
| ++num_times_all_advertisements_unregistered_; |
| } |
| |
| private: |
| size_t num_times_all_advertisements_unregistered_ = 0; |
| }; |
| |
| // Deletes the BleAdvertiser when notified. |
| class DeletingObserver final : public BleAdvertiser::Observer { |
| public: |
| DeletingObserver(std::unique_ptr<BleAdvertiserImpl>& ble_advertiser) |
| : ble_advertiser_(ble_advertiser) { |
| ble_advertiser_->AddObserver(this); |
| } |
| |
| ~DeletingObserver() override = default; |
| |
| // BleAdvertiser::Observer: |
| void OnAllAdvertisementsUnregistered() override { |
| ble_advertiser_->RemoveObserver(this); |
| ble_advertiser_.reset(); |
| } |
| |
| private: |
| std::unique_ptr<BleAdvertiserImpl>& ble_advertiser_; |
| }; |
| |
| } // namespace |
| |
| class BleAdvertiserImplTest : public testing::Test { |
| protected: |
| BleAdvertiserImplTest() |
| : fake_devices_(cryptauth::CreateRemoteDeviceRefListForTest(3)), |
| fake_advertisements_(GenerateFakeAdvertisements()) {} |
| |
| void SetUp() override { |
| fake_ble_service_data_helper_ = |
| std::make_unique<secure_channel::FakeBleServiceDataHelper>(); |
| fake_ble_service_data_helper_->SetAdvertisement( |
| secure_channel::DeviceIdPair(fake_devices_[0].GetDeviceId(), |
| kStubLocalDeviceId), |
| fake_advertisements_[0]); |
| fake_ble_service_data_helper_->SetAdvertisement( |
| secure_channel::DeviceIdPair(fake_devices_[1].GetDeviceId(), |
| kStubLocalDeviceId), |
| fake_advertisements_[1]); |
| fake_ble_service_data_helper_->SetAdvertisement( |
| secure_channel::DeviceIdPair(fake_devices_[2].GetDeviceId(), |
| kStubLocalDeviceId), |
| fake_advertisements_[2]); |
| |
| fake_ble_synchronizer_ = |
| std::make_unique<secure_channel::FakeBleSynchronizer>(); |
| |
| fake_advertisement_factory_ = |
| base::WrapUnique(new FakeErrorTolerantBleAdvertisementFactory()); |
| secure_channel::ErrorTolerantBleAdvertisementImpl::Factory:: |
| SetFactoryForTesting(fake_advertisement_factory_.get()); |
| |
| ble_advertiser_ = base::WrapUnique(new BleAdvertiserImpl( |
| fake_ble_service_data_helper_.get(), fake_ble_synchronizer_.get())); |
| |
| test_task_runner_ = base::MakeRefCounted<base::TestSimpleTaskRunner>(); |
| ble_advertiser_->SetTaskRunnerForTesting(test_task_runner_); |
| |
| test_observer_ = base::WrapUnique(new TestObserver()); |
| ble_advertiser_->AddObserver(test_observer_.get()); |
| } |
| |
| void TearDown() override { |
| secure_channel::ErrorTolerantBleAdvertisementImpl::Factory:: |
| SetFactoryForTesting(nullptr); |
| } |
| |
| void VerifyAdvertisementHasBeenStopped( |
| size_t index, |
| const std::string& expected_device_id) { |
| secure_channel::FakeErrorTolerantBleAdvertisement* advertisement = |
| fake_advertisement_factory_->active_advertisements()[index]; |
| EXPECT_EQ(expected_device_id, |
| advertisement->device_id_pair().remote_device_id()); |
| EXPECT_TRUE(advertisement->HasBeenStopped()); |
| } |
| |
| void InvokeAdvertisementStoppedCallback( |
| size_t index, |
| const std::string& expected_device_id) { |
| secure_channel::FakeErrorTolerantBleAdvertisement* advertisement = |
| fake_advertisement_factory_->active_advertisements()[index]; |
| EXPECT_EQ(expected_device_id, |
| advertisement->device_id_pair().remote_device_id()); |
| advertisement->InvokeStopCallback(); |
| } |
| |
| const base::test::ScopedTaskEnvironment scoped_task_environment_; |
| const cryptauth::RemoteDeviceRefList fake_devices_; |
| const std::vector<cryptauth::DataWithTimestamp> fake_advertisements_; |
| |
| std::unique_ptr<secure_channel::FakeBleServiceDataHelper> |
| fake_ble_service_data_helper_; |
| std::unique_ptr<secure_channel::FakeBleSynchronizer> fake_ble_synchronizer_; |
| |
| scoped_refptr<base::TestSimpleTaskRunner> test_task_runner_; |
| |
| std::unique_ptr<TestObserver> test_observer_; |
| |
| std::unique_ptr<FakeErrorTolerantBleAdvertisementFactory> |
| fake_advertisement_factory_; |
| |
| std::unique_ptr<BleAdvertiserImpl> ble_advertiser_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(BleAdvertiserImplTest); |
| }; |
| |
| TEST_F(BleAdvertiserImplTest, CannotGenerateAdvertisement) { |
| fake_ble_service_data_helper_->RemoveAdvertisement( |
| secure_channel::DeviceIdPair(fake_devices_[0].GetDeviceId(), |
| kStubLocalDeviceId)); |
| |
| EXPECT_FALSE(ble_advertiser_->StartAdvertisingToDevice( |
| fake_devices_[0].GetDeviceId())); |
| EXPECT_EQ(0u, fake_advertisement_factory_->num_created()); |
| EXPECT_EQ(0u, test_observer_->num_times_all_advertisements_unregistered()); |
| } |
| |
| // ryan |
| TEST_F(BleAdvertiserImplTest, AdvertisementRegisteredSuccessfully) { |
| EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice( |
| fake_devices_[0].GetDeviceId())); |
| EXPECT_EQ(1u, fake_advertisement_factory_->num_created()); |
| EXPECT_EQ(1u, fake_advertisement_factory_->active_advertisements().size()); |
| EXPECT_TRUE(ble_advertiser_->AreAdvertisementsRegistered()); |
| EXPECT_EQ(0u, test_observer_->num_times_all_advertisements_unregistered()); |
| |
| // Now, unregister. |
| EXPECT_TRUE( |
| ble_advertiser_->StopAdvertisingToDevice(fake_devices_[0].GetDeviceId())); |
| EXPECT_TRUE(ble_advertiser_->AreAdvertisementsRegistered()); |
| |
| // The advertisement should have been stopped, but it should not yet have |
| // been removed. |
| EXPECT_EQ(1u, fake_advertisement_factory_->active_advertisements().size()); |
| VerifyAdvertisementHasBeenStopped(0u /* index */, |
| fake_devices_[0].GetDeviceId()); |
| |
| // Invoke the stop callback and ensure the advertisement was deleted. |
| InvokeAdvertisementStoppedCallback(0u /* index */, |
| fake_devices_[0].GetDeviceId()); |
| test_task_runner_->RunUntilIdle(); |
| EXPECT_EQ(0u, fake_advertisement_factory_->active_advertisements().size()); |
| EXPECT_FALSE(ble_advertiser_->AreAdvertisementsRegistered()); |
| EXPECT_EQ(1u, test_observer_->num_times_all_advertisements_unregistered()); |
| } |
| |
| TEST_F(BleAdvertiserImplTest, AdvertisementRegisteredSuccessfully_TwoDevices) { |
| EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice( |
| fake_devices_[0].GetDeviceId())); |
| EXPECT_EQ(1u, fake_advertisement_factory_->num_created()); |
| EXPECT_EQ(1u, fake_advertisement_factory_->active_advertisements().size()); |
| EXPECT_TRUE(ble_advertiser_->AreAdvertisementsRegistered()); |
| EXPECT_EQ(0u, test_observer_->num_times_all_advertisements_unregistered()); |
| |
| EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice( |
| fake_devices_[1].GetDeviceId())); |
| EXPECT_EQ(2u, fake_advertisement_factory_->num_created()); |
| EXPECT_EQ(2u, fake_advertisement_factory_->active_advertisements().size()); |
| EXPECT_TRUE(ble_advertiser_->AreAdvertisementsRegistered()); |
| EXPECT_EQ(0u, test_observer_->num_times_all_advertisements_unregistered()); |
| |
| // Unregister device 0. |
| EXPECT_TRUE( |
| ble_advertiser_->StopAdvertisingToDevice(fake_devices_[0].GetDeviceId())); |
| EXPECT_EQ(2u, fake_advertisement_factory_->active_advertisements().size()); |
| InvokeAdvertisementStoppedCallback(0u /* index */, |
| fake_devices_[0].GetDeviceId()); |
| EXPECT_EQ(1u, fake_advertisement_factory_->active_advertisements().size()); |
| EXPECT_TRUE(ble_advertiser_->AreAdvertisementsRegistered()); |
| EXPECT_EQ(0u, test_observer_->num_times_all_advertisements_unregistered()); |
| |
| // Unregister device 1. |
| EXPECT_TRUE( |
| ble_advertiser_->StopAdvertisingToDevice(fake_devices_[1].GetDeviceId())); |
| EXPECT_EQ(1u, fake_advertisement_factory_->active_advertisements().size()); |
| InvokeAdvertisementStoppedCallback(0u /* index */, |
| fake_devices_[1].GetDeviceId()); |
| test_task_runner_->RunUntilIdle(); |
| EXPECT_EQ(0u, fake_advertisement_factory_->active_advertisements().size()); |
| EXPECT_FALSE(ble_advertiser_->AreAdvertisementsRegistered()); |
| EXPECT_EQ(1u, test_observer_->num_times_all_advertisements_unregistered()); |
| } |
| |
| TEST_F(BleAdvertiserImplTest, TooManyDevicesRegistered) { |
| ASSERT_EQ(2u, secure_channel::kMaxConcurrentAdvertisements); |
| |
| // Register device 0. |
| EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice( |
| fake_devices_[0].GetDeviceId())); |
| EXPECT_EQ(1u, fake_advertisement_factory_->num_created()); |
| EXPECT_EQ(1u, fake_advertisement_factory_->active_advertisements().size()); |
| |
| // Register device 1. |
| EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice( |
| fake_devices_[1].GetDeviceId())); |
| EXPECT_EQ(2u, fake_advertisement_factory_->num_created()); |
| EXPECT_EQ(2u, fake_advertisement_factory_->active_advertisements().size()); |
| |
| // Register device 2. This should fail, since it is over the limit. |
| EXPECT_FALSE(ble_advertiser_->StartAdvertisingToDevice( |
| fake_devices_[2].GetDeviceId())); |
| EXPECT_EQ(2u, fake_advertisement_factory_->num_created()); |
| EXPECT_EQ(2u, fake_advertisement_factory_->active_advertisements().size()); |
| |
| // Now, stop advertising to device 1. It should now be possible to advertise |
| // to device 2. |
| EXPECT_TRUE( |
| ble_advertiser_->StopAdvertisingToDevice(fake_devices_[1].GetDeviceId())); |
| EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice( |
| fake_devices_[2].GetDeviceId())); |
| |
| // However, the advertisement for device 1 should still be present, and no new |
| // advertisement for device 2 should have yet been created. |
| EXPECT_EQ(2u, fake_advertisement_factory_->num_created()); |
| EXPECT_EQ(2u, fake_advertisement_factory_->active_advertisements().size()); |
| VerifyAdvertisementHasBeenStopped(1u /* index */, |
| fake_devices_[1].GetDeviceId()); |
| |
| // Stop the advertisement for device 1. This should cause a new advertisement |
| // for device 2 to be created. |
| InvokeAdvertisementStoppedCallback(1u /* index */, |
| fake_devices_[1].GetDeviceId()); |
| test_task_runner_->RunUntilIdle(); |
| EXPECT_EQ(3u, fake_advertisement_factory_->num_created()); |
| EXPECT_EQ(2u, fake_advertisement_factory_->active_advertisements().size()); |
| |
| // Verify that the remaining active advertisements correspond to the correct |
| // devices. |
| EXPECT_EQ(fake_devices_[0].GetDeviceId(), |
| fake_advertisement_factory_->active_advertisements()[0] |
| ->device_id_pair() |
| .remote_device_id()); |
| EXPECT_EQ(fake_devices_[2].GetDeviceId(), |
| fake_advertisement_factory_->active_advertisements()[1] |
| ->device_id_pair() |
| .remote_device_id()); |
| } |
| |
| // Regression test for crbug.com/739883. This issue arises when the following |
| // occurs: |
| // (1) BleAdvertiserImpl starts advertising to device A. |
| // (2) BleAdvertiserImpl stops advertising to device A. The advertisement |
| // starts its asynchyronous unregistration flow. |
| // (3) BleAdvertiserImpl starts advertising to device A again, but the |
| // previous advertisement has not yet been fully unregistered. |
| // Before the fix for crbug.com/739883, this would cause an error of type |
| // ERROR_ADVERTISEMENT_ALREADY_EXISTS. However, the fix for this issue ensures |
| // that the new advertisement in step (3) above does not start until the |
| // previous one has been finished. |
| TEST_F(BleAdvertiserImplTest, SameAdvertisementAdded_FirstHasNotBeenStopped) { |
| EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice( |
| fake_devices_[0].GetDeviceId())); |
| EXPECT_EQ(1u, fake_advertisement_factory_->num_created()); |
| EXPECT_EQ(1u, fake_advertisement_factory_->active_advertisements().size()); |
| |
| // Unregister, but do not invoke the stop callback. |
| EXPECT_TRUE( |
| ble_advertiser_->StopAdvertisingToDevice(fake_devices_[0].GetDeviceId())); |
| VerifyAdvertisementHasBeenStopped(0u /* index */, |
| fake_devices_[0].GetDeviceId()); |
| |
| // Start advertising again, to the same device. Since the previous |
| // advertisement has not successfully stopped, no new advertisement should |
| // have been created yet. |
| EXPECT_TRUE(ble_advertiser_->StartAdvertisingToDevice( |
| fake_devices_[0].GetDeviceId())); |
| EXPECT_EQ(1u, fake_advertisement_factory_->num_created()); |
| EXPECT_EQ(1u, fake_advertisement_factory_->active_advertisements().size()); |
| |
| // Now, complete the previous stop. This should cause a new advertisement to |
| // be generated, but only the new one should be active. |
| InvokeAdvertisementStoppedCallback(0u /* index */, |
| fake_devices_[0].GetDeviceId()); |
| test_task_runner_->RunUntilIdle(); |
| EXPECT_EQ(2u, fake_advertisement_factory_->num_created()); |
| EXPECT_EQ(1u, fake_advertisement_factory_->active_advertisements().size()); |
| } |
| |
| // Regression test for crbug.com/776241. This bug could cause a crash if, when |
| // BleAdvertiserImpl notifies observers that all advertisements were |
| // unregistered, an observer deletes BleAdvertiserImpl. The fix for this issue |
| // is simply processing the next advertisement in a new task so that the new |
| // task will be canceled if the object is deleted. Without the fix for |
| // crbug.com/776241, this test would crash. |
| TEST_F(BleAdvertiserImplTest, ObserverDeletesObjectWhenNotified) { |
| // For this test, use a DeletingObserver instead. |
| DeletingObserver deleting_observer(ble_advertiser_); |
| ble_advertiser_->RemoveObserver(test_observer_.get()); |
| |
| ble_advertiser_->StartAdvertisingToDevice(fake_devices_[0].GetDeviceId()); |
| ble_advertiser_->StopAdvertisingToDevice(fake_devices_[0].GetDeviceId()); |
| InvokeAdvertisementStoppedCallback(0u /* index */, |
| fake_devices_[0].GetDeviceId()); |
| test_task_runner_->RunUntilIdle(); |
| } |
| |
| } // namespace tether |
| |
| } // namespace chromeos |