| // Copyright 2017 The Fuchsia 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 "src/connectivity/bluetooth/core/bt-host/hci/legacy_low_energy_advertiser.h" |
| |
| #include <fbl/macros.h> |
| |
| #include "src/connectivity/bluetooth/core/bt-host/common/device_address.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/test_helpers.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/uuid.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci/defaults.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci/fake_connection.h" |
| #include "src/connectivity/bluetooth/core/bt-host/testing/fake_controller.h" |
| #include "src/connectivity/bluetooth/core/bt-host/testing/fake_controller_test.h" |
| #include "src/connectivity/bluetooth/core/bt-host/testing/fake_peer.h" |
| |
| namespace bt { |
| |
| using testing::FakeController; |
| |
| namespace hci { |
| namespace { |
| |
| using TestingBase = bt::testing::FakeControllerTest<FakeController>; |
| |
| constexpr ConnectionHandle kHandle = 0x0001; |
| |
| const DeviceAddress kPublicAddress(DeviceAddress::Type::kLEPublic, |
| "00:00:00:00:00:01"); |
| const DeviceAddress kRandomAddress(DeviceAddress::Type::kLERandom, |
| "00:00:00:00:00:02"); |
| |
| constexpr size_t kDefaultAdSize = 20; |
| constexpr zx::duration kTestInterval = zx::sec(1); |
| |
| void NopConnectionCallback(ConnectionPtr) {} |
| |
| class HCI_LegacyLowEnergyAdvertiserTest : public TestingBase { |
| public: |
| HCI_LegacyLowEnergyAdvertiserTest() = default; |
| ~HCI_LegacyLowEnergyAdvertiserTest() override = default; |
| |
| protected: |
| // TestingBase overrides: |
| void SetUp() override { |
| TestingBase::SetUp(); |
| |
| // ACL data channel needs to be present for production hci::Connection |
| // objects. |
| TestingBase::InitializeACLDataChannel( |
| hci::DataBufferInfo(), |
| hci::DataBufferInfo(hci::kMaxACLPayloadSize, 10)); |
| |
| FakeController::Settings settings; |
| settings.ApplyLegacyLEConfig(); |
| settings.bd_addr = kPublicAddress; |
| test_device()->set_settings(settings); |
| |
| advertiser_ = std::make_unique<LegacyLowEnergyAdvertiser>(transport()); |
| |
| test_device()->StartCmdChannel(test_cmd_chan()); |
| test_device()->StartAclChannel(test_acl_chan()); |
| } |
| |
| void TearDown() override { |
| advertiser_ = nullptr; |
| test_device()->Stop(); |
| TestingBase::TearDown(); |
| } |
| |
| LegacyLowEnergyAdvertiser* advertiser() const { return advertiser_.get(); } |
| |
| LowEnergyAdvertiser::AdvertisingStatusCallback GetSuccessCallback() { |
| return [this](zx::duration interval, Status status) { |
| last_status_ = status; |
| EXPECT_TRUE(status) << status.ToString(); |
| }; |
| } |
| |
| LowEnergyAdvertiser::AdvertisingStatusCallback GetErrorCallback() { |
| return [this](zx::duration interval, Status status) { |
| last_status_ = status; |
| EXPECT_FALSE(status); |
| }; |
| } |
| |
| // Retrieves the last status, and resets the last status to empty. |
| std::optional<Status> MoveLastStatus() { return std::move(last_status_); } |
| |
| // Makes some fake advertising data of a specific |packed_size| |
| DynamicByteBuffer GetExampleData(size_t size = kDefaultAdSize) { |
| DynamicByteBuffer result(size); |
| // Count backwards. |
| for (size_t i = 0; i < size; i++) { |
| result[i] = (uint8_t)((size - i) % 255); |
| } |
| return result; |
| } |
| |
| private: |
| std::unique_ptr<LegacyLowEnergyAdvertiser> advertiser_; |
| |
| std::optional<Status> last_status_; |
| |
| DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(HCI_LegacyLowEnergyAdvertiserTest); |
| }; |
| |
| // TODO(jamuraa): Use typed tests to test LowEnergyAdvertiser common properties |
| |
| // - Error when the advertisement data is too large |
| TEST_F(HCI_LegacyLowEnergyAdvertiserTest, AdvertisementSizeTest) { |
| // 4 bytes long (adv length: 7 bytes) |
| auto reasonable_data = |
| CreateStaticByteBuffer(0x20, 0x06, 0xaa, 0xfe, 'T', 'e', 's', 't'); |
| // 30 bytes long (adv length: 33 bytes) |
| auto oversize_data = CreateStaticByteBuffer( |
| 0x20, 0x20, 0xaa, 0xfe, 'T', 'h', 'e', 'q', 'u', 'i', 'c', 'k', 'b', 'r', |
| 'o', 'w', 'n', 'f', 'o', 'x', 'w', 'a', 'g', 'g', 'e', 'd', 'i', 't', 's', |
| 't', 'a', 'i', 'l', '.'); |
| |
| DynamicByteBuffer scan_data; |
| |
| // Should accept ads that are of reasonable size |
| advertiser()->StartAdvertising(kPublicAddress, reasonable_data, scan_data, |
| nullptr, kTestInterval, false, |
| GetSuccessCallback()); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(MoveLastStatus()); |
| |
| advertiser()->StopAdvertising(kPublicAddress); |
| |
| // And reject ads that are too big |
| advertiser()->StartAdvertising(kPublicAddress, oversize_data, scan_data, |
| nullptr, kTestInterval, false, |
| GetErrorCallback()); |
| EXPECT_TRUE(MoveLastStatus()); |
| } |
| |
| // - Stops the advertisement when an incoming connection comes |
| // - Calls the connection callback correctly when it's setup |
| // - Checks that advertising state is cleaned up. |
| // - Checks that it is possible to restart advertising. |
| TEST_F(HCI_LegacyLowEnergyAdvertiserTest, ConnectionTest) { |
| DynamicByteBuffer ad = GetExampleData(); |
| DynamicByteBuffer scan_data; |
| |
| ConnectionPtr link; |
| auto conn_cb = [&link](auto cb_link) { link = std::move(cb_link); }; |
| |
| advertiser()->StartAdvertising(kPublicAddress, ad, scan_data, conn_cb, |
| kTestInterval, false, GetSuccessCallback()); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(MoveLastStatus()); |
| |
| // The connection manager will hand us a connection when one gets created. |
| advertiser()->OnIncomingConnection(kHandle, Connection::Role::kSlave, |
| kRandomAddress, LEConnectionParameters()); |
| ASSERT_TRUE(link); |
| EXPECT_EQ(kHandle, link->handle()); |
| EXPECT_EQ(kPublicAddress, link->local_address()); |
| EXPECT_EQ(kRandomAddress, link->peer_address()); |
| link->set_closed(); |
| |
| // Advertising state should get cleared. |
| RunLoopUntilIdle(); |
| |
| // StopAdvertising() sends multiple HCI commands. We only check that the |
| // first one succeeded. StartAdvertising cancels the rest of the sequence |
| // below. |
| EXPECT_FALSE(test_device()->le_advertising_state().enabled); |
| |
| // Restart advertising using kRandomAddress. |
| advertiser()->StartAdvertising(kRandomAddress, ad, scan_data, conn_cb, |
| kTestInterval, false, GetSuccessCallback()); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(MoveLastStatus()); |
| EXPECT_TRUE(test_device()->le_advertising_state().enabled); |
| |
| // Accept a connection from kPublicAddress. The local and peer addresses |
| // should get assigned correctly. |
| advertiser()->OnIncomingConnection(kHandle, Connection::Role::kSlave, |
| kPublicAddress, LEConnectionParameters()); |
| ASSERT_TRUE(link); |
| EXPECT_EQ(kRandomAddress, link->local_address()); |
| EXPECT_EQ(kPublicAddress, link->peer_address()); |
| link->set_closed(); |
| } |
| |
| // Tests that advertising can be restarted right away in a connection callback. |
| TEST_F(HCI_LegacyLowEnergyAdvertiserTest, RestartInConnectionCallback) { |
| DynamicByteBuffer ad = GetExampleData(); |
| DynamicByteBuffer scan_data; |
| |
| ConnectionPtr link; |
| auto conn_cb = [&, this](auto cb_link) { |
| link = std::move(cb_link); |
| advertiser()->StartAdvertising(kPublicAddress, ad, scan_data, |
| NopConnectionCallback, kTestInterval, false, |
| GetSuccessCallback()); |
| }; |
| |
| advertiser()->StartAdvertising(kPublicAddress, ad, scan_data, conn_cb, |
| kTestInterval, false, GetSuccessCallback()); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(MoveLastStatus()); |
| EXPECT_TRUE(test_device()->le_advertising_state().enabled); |
| |
| bool enabled = true; |
| std::vector<bool> adv_states; |
| test_device()->SetAdvertisingStateCallback( |
| [this, &adv_states, &enabled] { |
| bool new_enabled = test_device()->le_advertising_state().enabled; |
| if (enabled != new_enabled) { |
| adv_states.push_back(new_enabled); |
| enabled = new_enabled; |
| } |
| }, |
| dispatcher()); |
| |
| advertiser()->OnIncomingConnection(kHandle, Connection::Role::kSlave, |
| kRandomAddress, LEConnectionParameters()); |
| |
| // Advertising should get disabled and re-enabled. |
| RunLoopUntilIdle(); |
| ASSERT_EQ(2u, adv_states.size()); |
| EXPECT_FALSE(adv_states[0]); |
| EXPECT_TRUE(adv_states[1]); |
| } |
| |
| // Tests starting and stopping an advertisement. |
| TEST_F(HCI_LegacyLowEnergyAdvertiserTest, StartAndStop) { |
| constexpr zx::duration kInterval = zx::msec(500); |
| DynamicByteBuffer ad = GetExampleData(); |
| DynamicByteBuffer scan_data; |
| |
| advertiser()->StartAdvertising(kRandomAddress, ad, scan_data, nullptr, |
| kInterval, false, GetSuccessCallback()); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(MoveLastStatus()); |
| EXPECT_TRUE(test_device()->le_advertising_state().enabled); |
| |
| EXPECT_TRUE(advertiser()->StopAdvertising(kRandomAddress)); |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(test_device()->le_advertising_state().enabled); |
| } |
| |
| // Tests that an advertisement is configured with the correct parameters. |
| TEST_F(HCI_LegacyLowEnergyAdvertiserTest, AdvertisingParameters) { |
| constexpr zx::duration kInterval = zx::msec(500); |
| constexpr uint16_t kIntervalSlices = 800; |
| auto ad = GetExampleData(); |
| BufferView scan_data; |
| |
| advertiser()->StartAdvertising(kRandomAddress, ad, scan_data, nullptr, |
| kInterval, false, GetSuccessCallback()); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(MoveLastStatus()); |
| |
| // Verify the fake controller state. |
| const auto& fake_adv_state = test_device()->le_advertising_state(); |
| EXPECT_TRUE(fake_adv_state.enabled); |
| EXPECT_EQ(kIntervalSlices, fake_adv_state.interval); |
| EXPECT_EQ(fake_adv_state.advertised_view(), ad); |
| EXPECT_EQ(0u, fake_adv_state.scan_rsp_view().size()); |
| EXPECT_EQ(hci::LEOwnAddressType::kRandom, fake_adv_state.own_address_type); |
| |
| // Restart advertising with a public address and verify that the configured |
| // local address type is correct. |
| EXPECT_TRUE(advertiser()->StopAdvertising(kRandomAddress)); |
| advertiser()->StartAdvertising(kPublicAddress, ad, scan_data, nullptr, |
| kInterval, false, GetSuccessCallback()); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(MoveLastStatus()); |
| EXPECT_TRUE(fake_adv_state.enabled); |
| EXPECT_EQ(hci::LEOwnAddressType::kPublic, fake_adv_state.own_address_type); |
| } |
| |
| TEST_F(HCI_LegacyLowEnergyAdvertiserTest, StartWhileStarting) { |
| DynamicByteBuffer ad = GetExampleData(); |
| DynamicByteBuffer scan_data; |
| DeviceAddress addr = kRandomAddress; |
| |
| advertiser()->StartAdvertising(addr, ad, scan_data, nullptr, kTestInterval, |
| false, [](auto, auto) {}); |
| EXPECT_FALSE(test_device()->le_advertising_state().enabled); |
| |
| advertiser()->StartAdvertising(addr, ad, scan_data, nullptr, kTestInterval, |
| false, GetErrorCallback()); |
| EXPECT_FALSE(test_device()->le_advertising_state().enabled); |
| auto status = MoveLastStatus(); |
| ASSERT_TRUE(status); |
| EXPECT_EQ(HostError::kInProgress, status->error()); |
| } |
| |
| TEST_F(HCI_LegacyLowEnergyAdvertiserTest, StartWhileStopping) { |
| DynamicByteBuffer ad = GetExampleData(); |
| DynamicByteBuffer scan_data; |
| DeviceAddress addr = kRandomAddress; |
| |
| // Get to a started state. |
| advertiser()->StartAdvertising(addr, ad, scan_data, nullptr, kTestInterval, |
| false, GetSuccessCallback()); |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(MoveLastStatus()); |
| EXPECT_TRUE(test_device()->le_advertising_state().enabled); |
| |
| // Initiate a request to Stop and wait until it's partially in progress. |
| bool enabled = true; |
| bool was_disabled = false; |
| auto adv_state_cb = [&] { |
| enabled = test_device()->le_advertising_state().enabled; |
| if (!was_disabled && !enabled) { |
| was_disabled = true; |
| |
| // Starting now should cancel the stop sequence and succeed. |
| advertiser()->StartAdvertising(addr, ad, scan_data, nullptr, |
| kTestInterval, false, |
| GetSuccessCallback()); |
| } |
| }; |
| test_device()->SetAdvertisingStateCallback(adv_state_cb, dispatcher()); |
| |
| EXPECT_TRUE(advertiser()->StopAdvertising(addr)); |
| |
| // Advertising should have been momentarily disabled. |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(was_disabled); |
| EXPECT_TRUE(enabled); |
| EXPECT_TRUE(test_device()->le_advertising_state().enabled); |
| } |
| |
| // - StopAdvertisement noops when the advertisement address is wrong |
| // - Sets the advertisement data to null when stopped to prevent data leakage |
| // (re-enable advertising without changing data, intercept) |
| TEST_F(HCI_LegacyLowEnergyAdvertiserTest, StopAdvertisingConditions) { |
| DynamicByteBuffer ad = GetExampleData(); |
| DynamicByteBuffer scan_data; |
| |
| advertiser()->StartAdvertising(kRandomAddress, ad, scan_data, nullptr, |
| kTestInterval, false, GetSuccessCallback()); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(MoveLastStatus()); |
| |
| EXPECT_TRUE(test_device()->le_advertising_state().enabled); |
| EXPECT_TRUE(ContainersEqual( |
| test_device()->le_advertising_state().advertised_view(), ad)); |
| EXPECT_FALSE(advertiser()->StopAdvertising(kPublicAddress)); |
| |
| EXPECT_TRUE(test_device()->le_advertising_state().enabled); |
| EXPECT_TRUE(ContainersEqual( |
| test_device()->le_advertising_state().advertised_view(), ad)); |
| |
| EXPECT_TRUE(advertiser()->StopAdvertising(kRandomAddress)); |
| |
| RunLoopUntilIdle(); |
| |
| EXPECT_FALSE(test_device()->le_advertising_state().enabled); |
| EXPECT_EQ(0u, test_device()->le_advertising_state().advertised_view().size()); |
| EXPECT_EQ(0u, test_device()->le_advertising_state().scan_rsp_view().size()); |
| } |
| |
| // - Rejects StartAdvertising for a different address when Advertising already |
| TEST_F(HCI_LegacyLowEnergyAdvertiserTest, NoAdvertiseTwice) { |
| DynamicByteBuffer ad = GetExampleData(); |
| DynamicByteBuffer scan_data; |
| |
| advertiser()->StartAdvertising(kRandomAddress, ad, scan_data, nullptr, |
| kTestInterval, false, GetSuccessCallback()); |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(MoveLastStatus()); |
| EXPECT_TRUE(test_device()->le_advertising_state().enabled); |
| EXPECT_TRUE(ContainersEqual( |
| test_device()->le_advertising_state().advertised_view(), ad)); |
| EXPECT_EQ(hci::LEOwnAddressType::kRandom, |
| test_device()->le_advertising_state().own_address_type); |
| |
| uint8_t before = ad[0]; |
| ad[0] = 0xff; |
| advertiser()->StartAdvertising(kPublicAddress, ad, scan_data, nullptr, |
| kTestInterval, false, GetErrorCallback()); |
| ad[0] = before; |
| RunLoopUntilIdle(); |
| |
| // Should still be using the random address. |
| EXPECT_EQ(hci::LEOwnAddressType::kRandom, |
| test_device()->le_advertising_state().own_address_type); |
| EXPECT_TRUE(MoveLastStatus()); |
| EXPECT_TRUE(test_device()->le_advertising_state().enabled); |
| EXPECT_TRUE(ContainersEqual( |
| test_device()->le_advertising_state().advertised_view(), ad)); |
| } |
| |
| // - Updates data and params for the same address when advertising already |
| TEST_F(HCI_LegacyLowEnergyAdvertiserTest, AdvertiseUpdate) { |
| DynamicByteBuffer ad = GetExampleData(); |
| DynamicByteBuffer scan_data; |
| |
| advertiser()->StartAdvertising(kRandomAddress, ad, scan_data, nullptr, |
| kTestInterval, false, GetSuccessCallback()); |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(MoveLastStatus()); |
| EXPECT_TRUE(test_device()->le_advertising_state().enabled); |
| EXPECT_TRUE(ContainersEqual( |
| test_device()->le_advertising_state().advertised_view(), ad)); |
| |
| ad[0] = 0xff; |
| advertiser()->StartAdvertising(kRandomAddress, ad, scan_data, nullptr, |
| zx::msec(2500), false, GetSuccessCallback()); |
| RunLoopUntilIdle(); |
| |
| EXPECT_TRUE(MoveLastStatus()); |
| EXPECT_TRUE(test_device()->le_advertising_state().enabled); |
| EXPECT_TRUE(ContainersEqual( |
| test_device()->le_advertising_state().advertised_view(), ad)); |
| // 2500 ms = 4000 timeslices |
| EXPECT_EQ(4000, test_device()->le_advertising_state().interval); |
| } |
| |
| // - Rejects anonymous advertisement (unsupported) |
| TEST_F(HCI_LegacyLowEnergyAdvertiserTest, NoAnonymous) { |
| DynamicByteBuffer ad = GetExampleData(); |
| DynamicByteBuffer scan_data; |
| |
| advertiser()->StartAdvertising(kRandomAddress, ad, scan_data, nullptr, |
| kTestInterval, true, GetErrorCallback()); |
| EXPECT_TRUE(MoveLastStatus()); |
| EXPECT_FALSE(test_device()->le_advertising_state().enabled); |
| } |
| |
| TEST_F(HCI_LegacyLowEnergyAdvertiserTest, AllowsRandomAddressChange) { |
| // The random address can be changed while not advertising. |
| EXPECT_TRUE(advertiser()->AllowsRandomAddressChange()); |
| |
| // The random address cannot be changed while starting to advertise. |
| advertiser()->StartAdvertising(kRandomAddress, GetExampleData(), BufferView(), |
| nullptr, zx::sec(1), false, |
| GetSuccessCallback()); |
| EXPECT_FALSE(test_device()->le_advertising_state().enabled); |
| EXPECT_FALSE(advertiser()->AllowsRandomAddressChange()); |
| |
| // The random address cannot be changed while advertising is enabled. |
| RunLoopUntilIdle(); |
| EXPECT_TRUE(MoveLastStatus()); |
| EXPECT_TRUE(test_device()->le_advertising_state().enabled); |
| EXPECT_FALSE(advertiser()->AllowsRandomAddressChange()); |
| |
| // The advertiser allows changing the address while advertising is getting |
| // stopped. |
| advertiser()->StopAdvertising(kRandomAddress); |
| EXPECT_TRUE(test_device()->le_advertising_state().enabled); |
| EXPECT_TRUE(advertiser()->AllowsRandomAddressChange()); |
| |
| RunLoopUntilIdle(); |
| EXPECT_FALSE(test_device()->le_advertising_state().enabled); |
| EXPECT_TRUE(advertiser()->AllowsRandomAddressChange()); |
| } |
| |
| } // namespace |
| } // namespace hci |
| } // namespace bt |