| // Copyright 2023 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| #include "pw_bluetooth_sapphire/internal/host/hci/low_energy_advertiser.h" |
| |
| #include "pw_bluetooth_sapphire/internal/host/hci/android_extended_low_energy_advertiser.h" |
| #include "pw_bluetooth_sapphire/internal/host/hci/extended_low_energy_advertiser.h" |
| #include "pw_bluetooth_sapphire/internal/host/hci/legacy_low_energy_advertiser.h" |
| #include "pw_bluetooth_sapphire/internal/host/testing/controller_test.h" |
| #include "pw_bluetooth_sapphire/internal/host/testing/fake_controller.h" |
| #include "pw_bluetooth_sapphire/internal/host/testing/fake_peer.h" |
| #include "pw_bluetooth_sapphire/internal/host/testing/test_helpers.h" |
| |
| // LowEnergyAdvertiser has many potential subclasses (e.g. |
| // LegacyLowEnergyAdvertiser, ExtendedLowEnergyAdvertiser, |
| // AndroidExtendedLowEnergyAdvertiser, etc). The unique features of these |
| // subclass are tested individually in their own unittest files. However, there |
| // are some common features that all LowEnergyAdvertisers should follow. This |
| // class implements a type parameterized test to exercise those common features. |
| // |
| // If you add a new subclass of LowEnergyAdvertiser in the future, make sure to |
| // add its type to the list of types below (in the TYPED_TEST_SUITE) so that its |
| // common features are exercised as well. |
| |
| namespace bt::hci { |
| namespace { |
| |
| using bt::testing::FakeController; |
| using bt::testing::FakePeer; |
| |
| using AdvertisingOptions = LowEnergyAdvertiser::AdvertisingOptions; |
| using TestingBase = bt::testing::FakeDispatcherControllerTest<FakeController>; |
| |
| constexpr hci_spec::ConnectionHandle kConnectionHandle = 0x0001; |
| constexpr AdvertisingIntervalRange kTestInterval( |
| hci_spec::kLEAdvertisingIntervalMin, hci_spec::kLEAdvertisingIntervalMax); |
| |
| const DeviceAddress kPublicAddress(DeviceAddress::Type::kLEPublic, {1}); |
| const DeviceAddress kRandomAddress(DeviceAddress::Type::kLERandom, {2}); |
| |
| // Various parts of the Bluetooth Core Spec require that advertising interval |
| // min and max are not the same value. We shouldn't allow it either. For |
| // example, Core Spec Volume 4, Part E, Section 7.8.5: "The |
| // Advertising_Interval_Min and Advertising_Interval_Max should not be the same |
| // value to enable the Controller to determine the best advertising interval |
| // given other activities." |
| TEST(AdvertisingIntervalRangeDeathTest, MaxMinNotSame) { |
| EXPECT_DEATH(AdvertisingIntervalRange(hci_spec::kLEAdvertisingIntervalMin, |
| hci_spec::kLEAdvertisingIntervalMin), |
| ".*"); |
| } |
| |
| TEST(AdvertisingIntervalRangeDeathTest, MinLessThanMax) { |
| EXPECT_DEATH(AdvertisingIntervalRange(hci_spec::kLEAdvertisingIntervalMax, |
| hci_spec::kLEAdvertisingIntervalMin), |
| ".*"); |
| } |
| |
| template <typename T> |
| class LowEnergyAdvertiserTest : public TestingBase { |
| public: |
| LowEnergyAdvertiserTest() = default; |
| ~LowEnergyAdvertiserTest() override = default; |
| |
| protected: |
| void SetUp() override { |
| TestingBase::SetUp(); |
| |
| // ACL data channel needs to be present for production hci::Connection |
| // objects. |
| TestingBase::InitializeACLDataChannel( |
| hci::DataBufferInfo(), |
| hci::DataBufferInfo(hci_spec::kMaxACLPayloadSize, 10)); |
| |
| FakeController::Settings settings; |
| settings.ApplyLegacyLEConfig(); |
| settings.bd_addr = kPublicAddress; |
| test_device()->set_settings(settings); |
| |
| advertiser_ = std::unique_ptr<T>(CreateAdvertiserInternal()); |
| |
| test_device()->set_num_supported_advertising_sets(0xEF); |
| } |
| |
| void TearDown() override { |
| advertiser_ = nullptr; |
| TestingBase::TearDown(); |
| } |
| |
| template <bool same = std::is_same_v<T, AndroidExtendedLowEnergyAdvertiser>> |
| std::enable_if_t<same, AndroidExtendedLowEnergyAdvertiser>* |
| CreateAdvertiserInternal() { |
| return new AndroidExtendedLowEnergyAdvertiser(transport()->GetWeakPtr(), 1); |
| } |
| |
| template <bool same = std::is_same_v<T, ExtendedLowEnergyAdvertiser>> |
| std::enable_if_t<same, ExtendedLowEnergyAdvertiser>* |
| CreateAdvertiserInternal() { |
| return new ExtendedLowEnergyAdvertiser(transport()->GetWeakPtr()); |
| } |
| |
| template <bool same = std::is_same_v<T, LegacyLowEnergyAdvertiser>> |
| std::enable_if_t<same, LegacyLowEnergyAdvertiser>* |
| CreateAdvertiserInternal() { |
| return new LegacyLowEnergyAdvertiser(transport()->GetWeakPtr()); |
| } |
| |
| LowEnergyAdvertiser* advertiser() const { return advertiser_.get(); } |
| |
| void DestroyAdvertiser() { advertiser_.reset(); } |
| |
| ResultFunction<> MakeExpectSuccessCallback() { |
| return [this](Result<> status) { |
| last_status_ = status; |
| EXPECT_EQ(fit::ok(), status); |
| }; |
| } |
| |
| ResultFunction<> MakeExpectErrorCallback() { |
| return [this](Result<> status) { |
| last_status_ = status; |
| EXPECT_TRUE(status.is_error()); |
| }; |
| } |
| |
| std::optional<Result<>> GetLastStatus() { |
| if (!last_status_) { |
| return std::nullopt; |
| } |
| |
| Result<> status = last_status_.value(); |
| last_status_.reset(); |
| return status; |
| } |
| |
| // Makes some fake advertising data. |
| // |include_flags| signals whether to include flag encoding size in the data |
| // calculation. |
| AdvertisingData GetExampleData(bool include_flags = true) { |
| AdvertisingData result; |
| |
| auto name = "fuchsia"; |
| EXPECT_TRUE(result.SetLocalName(name)); |
| |
| auto appearance = 0x1234; |
| result.SetAppearance(appearance); |
| |
| EXPECT_LE(result.CalculateBlockSize(include_flags), |
| hci_spec::kMaxLEAdvertisingDataLength); |
| return result; |
| } |
| |
| // Makes fake advertising data that is too large. |
| // |include_flags| signals whether to include flag encoding size in the data |
| // calculation. |
| AdvertisingData GetTooLargeExampleData( |
| bool include_flags, |
| std::size_t size = hci_spec::kMaxLEAdvertisingDataLength + 1) { |
| AdvertisingData result; |
| |
| if (include_flags) { |
| size -= kTLVFlagsSize; |
| } |
| |
| std::ostringstream oss; |
| for (unsigned int i = 0; i <= size; i++) { |
| oss << 'a'; |
| } |
| |
| EXPECT_TRUE(result.SetLocalName(oss.str())); |
| |
| // The maximum advertisement packet is: |
| // |hci_spec::kMaxLEAdvertisingDataLength| = 31, and |result| = 32 bytes. |
| // |result| should be too large to advertise. |
| EXPECT_GT(result.CalculateBlockSize(include_flags), |
| hci_spec::kMaxLEAdvertisingDataLength); |
| return result; |
| } |
| |
| std::optional<hci_spec::AdvertisingHandle> CurrentAdvertisingHandle() const { |
| if (std::is_same_v<T, ExtendedLowEnergyAdvertiser>) { |
| auto extended = static_cast<ExtendedLowEnergyAdvertiser*>(advertiser()); |
| return extended->LastUsedHandleForTesting(); |
| } |
| |
| if (std::is_same_v<T, AndroidExtendedLowEnergyAdvertiser>) { |
| auto extended = |
| static_cast<AndroidExtendedLowEnergyAdvertiser*>(advertiser()); |
| return extended->LastUsedHandleForTesting(); |
| } |
| |
| return 0; // non-extended advertising doesn't use handles, we can return |
| // any value |
| } |
| |
| const FakeController::LEAdvertisingState& GetControllerAdvertisingState() |
| const { |
| if (std::is_same_v<T, LegacyLowEnergyAdvertiser>) { |
| return test_device()->legacy_advertising_state(); |
| } |
| |
| if (std::is_same_v<T, ExtendedLowEnergyAdvertiser> || |
| std::is_same_v<T, AndroidExtendedLowEnergyAdvertiser>) { |
| std::optional<hci_spec::AdvertisingHandle> handle = |
| CurrentAdvertisingHandle(); |
| if (!handle) { |
| static FakeController::LEAdvertisingState empty; |
| return empty; |
| } |
| |
| return test_device()->extended_advertising_state(handle.value()); |
| } |
| |
| EXPECT_TRUE(false) << "advertiser is of unknown type"; |
| |
| // return something in order to compile, tests will fail if they get here |
| static FakeController::LEAdvertisingState state; |
| return state; |
| } |
| |
| void MaybeSendMultipleAdvertisingPostConnectionEvents( |
| hci_spec::ConnectionHandle conn_handle, |
| hci_spec::AdvertisingHandle adv_handle) { |
| if (std::is_same_v<T, AndroidExtendedLowEnergyAdvertiser>) { |
| test_device()->SendAndroidLEMultipleAdvertisingStateChangeSubevent( |
| conn_handle, adv_handle); |
| return; |
| } |
| |
| if (std::is_same_v<T, ExtendedLowEnergyAdvertiser>) { |
| test_device()->SendLEAdvertisingSetTerminatedEvent(conn_handle, |
| adv_handle); |
| return; |
| } |
| } |
| |
| private: |
| std::unique_ptr<LowEnergyAdvertiser> advertiser_; |
| std::optional<Result<>> last_status_; |
| |
| BT_DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(LowEnergyAdvertiserTest); |
| }; |
| |
| using Implementations = ::testing::Types<LegacyLowEnergyAdvertiser, |
| ExtendedLowEnergyAdvertiser, |
| AndroidExtendedLowEnergyAdvertiser>; |
| TYPED_TEST_SUITE(LowEnergyAdvertiserTest, Implementations); |
| |
| // - Stops the advertisement when an incoming connection comes in |
| // - Possible to restart advertising |
| // - Advertising state cleaned up between calls |
| TYPED_TEST(LowEnergyAdvertiserTest, ConnectionTest) { |
| AdvertisingData adv_data = this->GetExampleData(); |
| AdvertisingData scan_data = this->GetExampleData(); |
| AdvertisingOptions options(kTestInterval, |
| /*anonymous=*/false, |
| kDefaultNoAdvFlags, |
| /*include_tx_power_level=*/false); |
| |
| std::unique_ptr<LowEnergyConnection> link; |
| auto conn_cb = [&link](auto cb_link) { link = std::move(cb_link); }; |
| |
| // Start advertising kPublicAddress |
| this->advertiser()->StartAdvertising(kPublicAddress, |
| adv_data, |
| scan_data, |
| options, |
| conn_cb, |
| this->MakeExpectSuccessCallback()); |
| this->RunUntilIdle(); |
| EXPECT_TRUE(this->GetLastStatus()); |
| EXPECT_TRUE(this->advertiser()->IsAdvertising()); |
| EXPECT_TRUE(this->advertiser()->IsAdvertising(kPublicAddress)); |
| |
| // Accept a connection and ensure that connection state is set up correctly |
| link.reset(); |
| this->advertiser()->OnIncomingConnection( |
| kConnectionHandle, |
| pw::bluetooth::emboss::ConnectionRole::PERIPHERAL, |
| kRandomAddress, |
| hci_spec::LEConnectionParameters()); |
| std::optional<hci_spec::AdvertisingHandle> handle = |
| this->CurrentAdvertisingHandle(); |
| ASSERT_TRUE(handle); |
| this->MaybeSendMultipleAdvertisingPostConnectionEvents(kConnectionHandle, |
| handle.value()); |
| this->RunUntilIdle(); |
| |
| ASSERT_TRUE(link); |
| EXPECT_EQ(kConnectionHandle, link->handle()); |
| EXPECT_EQ(kPublicAddress, link->local_address()); |
| EXPECT_EQ(kRandomAddress, link->peer_address()); |
| EXPECT_FALSE(this->advertiser()->IsAdvertising()); |
| EXPECT_FALSE(this->advertiser()->IsAdvertising(kPublicAddress)); |
| |
| // Advertising state should get cleared on a disconnection |
| link->Disconnect( |
| pw::bluetooth::emboss::StatusCode::REMOTE_USER_TERMINATED_CONNECTION); |
| this->test_device()->SendDisconnectionCompleteEvent(link->handle()); |
| this->RunUntilIdle(); |
| EXPECT_FALSE(this->GetControllerAdvertisingState().enabled); |
| |
| // Restart advertising using a different local address |
| this->advertiser()->StartAdvertising(kRandomAddress, |
| adv_data, |
| scan_data, |
| options, |
| conn_cb, |
| this->MakeExpectSuccessCallback()); |
| this->RunUntilIdle(); |
| EXPECT_TRUE(this->GetLastStatus()); |
| EXPECT_TRUE(this->GetControllerAdvertisingState().enabled); |
| |
| // Accept a connection from kPublicAddress. The internal advertising state |
| // should get assigned correctly with no remnants of the previous advertise. |
| link.reset(); |
| this->advertiser()->OnIncomingConnection( |
| kConnectionHandle, |
| pw::bluetooth::emboss::ConnectionRole::PERIPHERAL, |
| kPublicAddress, |
| hci_spec::LEConnectionParameters()); |
| handle = this->CurrentAdvertisingHandle(); |
| ASSERT_TRUE(handle); |
| this->MaybeSendMultipleAdvertisingPostConnectionEvents(kConnectionHandle, |
| handle.value()); |
| this->RunUntilIdle(); |
| |
| ASSERT_TRUE(link); |
| EXPECT_EQ(kRandomAddress, link->local_address()); |
| EXPECT_EQ(kPublicAddress, link->peer_address()); |
| } |
| |
| // Tests that advertising can be restarted right away in a connection callback. |
| TYPED_TEST(LowEnergyAdvertiserTest, RestartInConnectionCallback) { |
| AdvertisingData ad = this->GetExampleData(); |
| AdvertisingData scan_data = this->GetExampleData(); |
| AdvertisingOptions options(kTestInterval, |
| /*anonymous=*/false, |
| kDefaultNoAdvFlags, |
| /*include_tx_power_level=*/false); |
| |
| std::unique_ptr<LowEnergyConnection> link; |
| auto conn_cb = [&, this](auto cb_link) { |
| link = std::move(cb_link); |
| |
| this->advertiser()->StartAdvertising( |
| kPublicAddress, |
| ad, |
| scan_data, |
| options, |
| [](auto) { /*noop*/ }, |
| this->MakeExpectSuccessCallback()); |
| }; |
| |
| this->advertiser()->StartAdvertising(kPublicAddress, |
| ad, |
| scan_data, |
| options, |
| conn_cb, |
| this->MakeExpectSuccessCallback()); |
| this->RunUntilIdle(); |
| EXPECT_TRUE(this->GetLastStatus()); |
| EXPECT_TRUE(this->GetControllerAdvertisingState().enabled); |
| |
| bool enabled = true; |
| std::vector<bool> adv_states; |
| this->test_device()->set_advertising_state_callback( |
| [this, &adv_states, &enabled] { |
| bool new_enabled = this->GetControllerAdvertisingState().enabled; |
| if (enabled != new_enabled) { |
| adv_states.push_back(new_enabled); |
| enabled = new_enabled; |
| } |
| }); |
| |
| this->advertiser()->OnIncomingConnection( |
| kConnectionHandle, |
| pw::bluetooth::emboss::ConnectionRole::PERIPHERAL, |
| kRandomAddress, |
| hci_spec::LEConnectionParameters()); |
| std::optional<hci_spec::AdvertisingHandle> handle = |
| this->CurrentAdvertisingHandle(); |
| ASSERT_TRUE(handle); |
| this->MaybeSendMultipleAdvertisingPostConnectionEvents(kConnectionHandle, |
| handle.value()); |
| |
| // Advertising should get disabled and re-enabled. |
| this->RunUntilIdle(); |
| ASSERT_EQ(2u, adv_states.size()); |
| EXPECT_FALSE(adv_states[0]); |
| EXPECT_TRUE(adv_states[1]); |
| |
| this->test_device()->set_advertising_state_callback(nullptr); |
| } |
| |
| // An incoming connection when not advertising should get disconnected. |
| TYPED_TEST(LowEnergyAdvertiserTest, IncomingConnectionWhenNotAdvertising) { |
| std::vector<std::pair<bool, hci_spec::ConnectionHandle>> connection_states; |
| this->test_device()->set_connection_state_callback( |
| [&](const auto& address, auto handle, bool connected, bool canceled) { |
| EXPECT_EQ(kRandomAddress, address); |
| EXPECT_FALSE(canceled); |
| connection_states.push_back(std::make_pair(connected, handle)); |
| }); |
| |
| auto fake_peer = std::make_unique<FakePeer>( |
| kRandomAddress, TestFixture::dispatcher(), true, true); |
| this->test_device()->AddPeer(std::move(fake_peer)); |
| this->test_device()->ConnectLowEnergy( |
| kRandomAddress, pw::bluetooth::emboss::ConnectionRole::PERIPHERAL); |
| this->RunUntilIdle(); |
| |
| ASSERT_EQ(1u, connection_states.size()); |
| auto [connection_state, handle] = connection_states[0]; |
| EXPECT_TRUE(connection_state); |
| |
| // Notify the advertiser of the incoming connection. It should reject it and |
| // the controller should become disconnected. |
| this->advertiser()->OnIncomingConnection( |
| handle, |
| pw::bluetooth::emboss::ConnectionRole::PERIPHERAL, |
| kRandomAddress, |
| hci_spec::LEConnectionParameters()); |
| this->MaybeSendMultipleAdvertisingPostConnectionEvents(kConnectionHandle, 0); |
| this->RunUntilIdle(); |
| ASSERT_EQ(2u, connection_states.size()); |
| auto [connection_state_after_disconnect, disconnected_handle] = |
| connection_states[1]; |
| EXPECT_EQ(handle, disconnected_handle); |
| EXPECT_FALSE(connection_state_after_disconnect); |
| } |
| |
| // An incoming connection during non-connectable advertising should get |
| // disconnected. |
| TYPED_TEST(LowEnergyAdvertiserTest, |
| IncomingConnectionWhenNonConnectableAdvertising) { |
| AdvertisingData ad = this->GetExampleData(); |
| AdvertisingData scan_data = this->GetExampleData(); |
| AdvertisingOptions options(kTestInterval, |
| /*anonymous=*/false, |
| kDefaultNoAdvFlags, |
| /*include_tx_power_level=*/false); |
| this->advertiser()->StartAdvertising(kPublicAddress, |
| ad, |
| scan_data, |
| options, |
| nullptr, |
| this->MakeExpectSuccessCallback()); |
| this->RunUntilIdle(); |
| ASSERT_TRUE(this->GetLastStatus()); |
| |
| std::vector<std::pair<bool, hci_spec::ConnectionHandle>> connection_states; |
| this->test_device()->set_connection_state_callback( |
| [&](const auto& address, auto handle, bool connected, bool canceled) { |
| EXPECT_EQ(kRandomAddress, address); |
| EXPECT_FALSE(canceled); |
| connection_states.push_back(std::make_pair(connected, handle)); |
| }); |
| |
| auto fake_peer = std::make_unique<FakePeer>( |
| kRandomAddress, TestFixture::dispatcher(), true, true); |
| this->test_device()->AddPeer(std::move(fake_peer)); |
| this->test_device()->ConnectLowEnergy( |
| kRandomAddress, pw::bluetooth::emboss::ConnectionRole::PERIPHERAL); |
| this->RunUntilIdle(); |
| |
| ASSERT_EQ(1u, connection_states.size()); |
| auto [connection_state, handle] = connection_states[0]; |
| EXPECT_TRUE(connection_state); |
| |
| // Notify the advertiser of the incoming connection. It should reject it and |
| // the controller should become disconnected. |
| this->advertiser()->OnIncomingConnection( |
| handle, |
| pw::bluetooth::emboss::ConnectionRole::PERIPHERAL, |
| kRandomAddress, |
| hci_spec::LEConnectionParameters()); |
| this->MaybeSendMultipleAdvertisingPostConnectionEvents(kConnectionHandle, 0); |
| this->RunUntilIdle(); |
| ASSERT_EQ(2u, connection_states.size()); |
| auto [connection_state_after_disconnect, disconnected_handle] = |
| connection_states[1]; |
| EXPECT_EQ(handle, disconnected_handle); |
| EXPECT_FALSE(connection_state_after_disconnect); |
| } |
| |
| // Tests starting and stopping an advertisement. |
| TYPED_TEST(LowEnergyAdvertiserTest, StartAndStop) { |
| AdvertisingData ad = this->GetExampleData(); |
| AdvertisingData scan_data = this->GetExampleData(); |
| AdvertisingOptions options(kTestInterval, |
| /*anonymous=*/false, |
| kDefaultNoAdvFlags, |
| /*include_tx_power_level=*/false); |
| |
| this->advertiser()->StartAdvertising(kRandomAddress, |
| ad, |
| scan_data, |
| options, |
| nullptr, |
| this->MakeExpectSuccessCallback()); |
| this->RunUntilIdle(); |
| EXPECT_TRUE(this->GetLastStatus()); |
| EXPECT_TRUE(this->GetControllerAdvertisingState().enabled); |
| |
| this->advertiser()->StopAdvertising(kRandomAddress); |
| this->RunUntilIdle(); |
| EXPECT_FALSE(this->GetControllerAdvertisingState().enabled); |
| } |
| |
| // Tests that an advertisement is configured with the correct parameters. |
| TYPED_TEST(LowEnergyAdvertiserTest, AdvertisingParameters) { |
| AdvertisingData ad = this->GetExampleData(); |
| AdvertisingData scan_data = this->GetExampleData(); |
| auto flags = AdvFlag::kLEGeneralDiscoverableMode; |
| AdvertisingOptions options(kTestInterval, |
| /*anonymous=*/false, |
| flags, |
| /*include_tx_power_level=*/false); |
| |
| this->advertiser()->StartAdvertising(kRandomAddress, |
| ad, |
| scan_data, |
| options, |
| nullptr, |
| this->MakeExpectSuccessCallback()); |
| this->RunUntilIdle(); |
| EXPECT_TRUE(this->GetLastStatus()); |
| |
| // The expected advertisement including the Flags. |
| DynamicByteBuffer expected_ad(ad.CalculateBlockSize(/*include_flags=*/true)); |
| ad.WriteBlock(&expected_ad, flags); |
| |
| DynamicByteBuffer expected_scan_data( |
| scan_data.CalculateBlockSize(/*include_flags=*/false)); |
| scan_data.WriteBlock(&expected_scan_data, std::nullopt); |
| |
| std::optional<FakeController::LEAdvertisingState> state = |
| this->GetControllerAdvertisingState(); |
| EXPECT_TRUE(state); |
| EXPECT_TRUE(state->enabled); |
| EXPECT_EQ(kTestInterval.min(), state->interval_min); |
| EXPECT_EQ(kTestInterval.max(), state->interval_max); |
| EXPECT_EQ(expected_ad, state->advertised_view()); |
| EXPECT_EQ(expected_scan_data, state->scan_rsp_view()); |
| EXPECT_EQ(pw::bluetooth::emboss::LEOwnAddressType::RANDOM, |
| state->own_address_type); |
| |
| // Restart advertising with a public address and verify that the configured |
| // local address type is correct. |
| this->advertiser()->StopAdvertising(kRandomAddress); |
| AdvertisingOptions new_options(kTestInterval, |
| /*anonymous=*/false, |
| kDefaultNoAdvFlags, |
| /*include_tx_power_level=*/false); |
| this->advertiser()->StartAdvertising(kPublicAddress, |
| ad, |
| scan_data, |
| new_options, |
| nullptr, |
| this->MakeExpectSuccessCallback()); |
| this->RunUntilIdle(); |
| EXPECT_TRUE(this->GetLastStatus()); |
| |
| state = this->GetControllerAdvertisingState(); |
| EXPECT_TRUE(state); |
| EXPECT_TRUE(state->enabled); |
| EXPECT_EQ(pw::bluetooth::emboss::LEOwnAddressType::PUBLIC, |
| state->own_address_type); |
| } |
| |
| // Tests that a previous advertisement's advertising data isn't accidentally |
| // kept around. This test was added to address fxbug.dev/42081491. |
| TYPED_TEST(LowEnergyAdvertiserTest, PreviousAdvertisingParameters) { |
| AdvertisingData scan_data = this->GetExampleData(); |
| auto flags = AdvFlag::kLEGeneralDiscoverableMode; |
| AdvertisingOptions options(kTestInterval, |
| /*anonymous=*/false, |
| flags, |
| /*include_tx_power_level=*/false); |
| |
| // old advertising data: ideally we would fill this completely so that in the |
| // next start advertising call, we can confirm that none of the remnants are |
| // left. However, doing so results in the advertising data being too long. |
| // Instead, we rely on other unit tests within advertising data unittests to |
| // ensure all previous remnants are removed. |
| AdvertisingData ad; |
| ad.SetTxPower(4); |
| ad.SetAppearance(0x4567); |
| EXPECT_TRUE(ad.SetLocalName("fuchsia")); |
| |
| this->advertiser()->StartAdvertising(kRandomAddress, |
| ad, |
| scan_data, |
| options, |
| nullptr, |
| this->MakeExpectSuccessCallback()); |
| this->RunUntilIdle(); |
| EXPECT_TRUE(this->GetLastStatus()); |
| |
| // new advertising data (with fewer fields filled in) |
| this->advertiser()->StopAdvertising(kRandomAddress); |
| AdvertisingData new_ad = this->GetExampleData(); |
| this->advertiser()->StartAdvertising(kRandomAddress, |
| new_ad, |
| scan_data, |
| options, |
| nullptr, |
| this->MakeExpectSuccessCallback()); |
| this->RunUntilIdle(); |
| EXPECT_TRUE(this->GetLastStatus()); |
| |
| DynamicByteBuffer expected_ad( |
| new_ad.CalculateBlockSize(/*include_flags=*/true)); |
| new_ad.WriteBlock(&expected_ad, flags); |
| |
| std::optional<FakeController::LEAdvertisingState> state = |
| this->GetControllerAdvertisingState(); |
| EXPECT_EQ(expected_ad, state->advertised_view()); |
| } |
| |
| // Tests that advertising interval values are capped within the allowed range. |
| TYPED_TEST(LowEnergyAdvertiserTest, AdvertisingIntervalWithinAllowedRange) { |
| AdvertisingData ad = this->GetExampleData(); |
| AdvertisingData scan_data = this->GetExampleData(); |
| |
| // Pass min and max values that are outside the allowed range. These should be |
| // capped. |
| constexpr AdvertisingIntervalRange interval( |
| hci_spec::kLEAdvertisingIntervalMin - 1, |
| hci_spec::kLEAdvertisingIntervalMax + 1); |
| AdvertisingOptions options(interval, |
| /*anonymous=*/false, |
| kDefaultNoAdvFlags, |
| /*include_tx_power_level=*/false); |
| this->advertiser()->StartAdvertising(kRandomAddress, |
| ad, |
| scan_data, |
| options, |
| nullptr, |
| this->MakeExpectSuccessCallback()); |
| this->RunUntilIdle(); |
| EXPECT_TRUE(this->GetLastStatus()); |
| |
| std::optional<FakeController::LEAdvertisingState> state = |
| this->GetControllerAdvertisingState(); |
| EXPECT_TRUE(state); |
| EXPECT_EQ(hci_spec::kLEAdvertisingIntervalMin, state->interval_min); |
| EXPECT_EQ(hci_spec::kLEAdvertisingIntervalMax, state->interval_max); |
| |
| // Reconfigure with values that are within the range. These should get passed |
| // down as is. |
| const AdvertisingIntervalRange new_interval( |
| hci_spec::kLEAdvertisingIntervalMin + 1, |
| hci_spec::kLEAdvertisingIntervalMax - 1); |
| AdvertisingOptions new_options(new_interval, |
| /*anonymous=*/false, |
| kDefaultNoAdvFlags, |
| /*include_tx_power_level=*/false); |
| this->advertiser()->StartAdvertising(kRandomAddress, |
| ad, |
| scan_data, |
| new_options, |
| nullptr, |
| this->MakeExpectSuccessCallback()); |
| this->RunUntilIdle(); |
| EXPECT_TRUE(this->GetLastStatus()); |
| |
| state = this->GetControllerAdvertisingState(); |
| EXPECT_TRUE(state); |
| EXPECT_EQ(new_interval.min(), state->interval_min); |
| EXPECT_EQ(new_interval.max(), state->interval_max); |
| } |
| |
| TYPED_TEST(LowEnergyAdvertiserTest, StartWhileStarting) { |
| AdvertisingData ad = this->GetExampleData(); |
| AdvertisingData scan_data = this->GetExampleData(); |
| DeviceAddress addr = kRandomAddress; |
| |
| const AdvertisingIntervalRange old_interval = kTestInterval; |
| AdvertisingOptions old_options(old_interval, |
| /*anonymous=*/false, |
| kDefaultNoAdvFlags, |
| /*include_tx_power_level=*/false); |
| const AdvertisingIntervalRange new_interval(kTestInterval.min() + 1, |
| kTestInterval.max() - 1); |
| AdvertisingOptions new_options(new_interval, |
| /*anonymous=*/false, |
| kDefaultNoAdvFlags, |
| /*include_tx_power_level=*/false); |
| |
| this->advertiser()->StartAdvertising( |
| addr, ad, scan_data, old_options, nullptr, [](auto) {}); |
| EXPECT_FALSE(this->GetControllerAdvertisingState().enabled); |
| |
| // This call should override the previous call and succeed with the new |
| // parameters. |
| this->advertiser()->StartAdvertising(addr, |
| ad, |
| scan_data, |
| new_options, |
| nullptr, |
| this->MakeExpectSuccessCallback()); |
| this->RunUntilIdle(); |
| EXPECT_TRUE(this->GetLastStatus()); |
| EXPECT_TRUE(this->GetControllerAdvertisingState().enabled); |
| EXPECT_EQ(new_interval.max(), |
| this->GetControllerAdvertisingState().interval_max); |
| } |
| |
| TYPED_TEST(LowEnergyAdvertiserTest, StartWhileStopping) { |
| AdvertisingData ad = this->GetExampleData(); |
| AdvertisingData scan_data = this->GetExampleData(); |
| DeviceAddress addr = kRandomAddress; |
| AdvertisingOptions options(kTestInterval, |
| /*anonymous=*/false, |
| kDefaultNoAdvFlags, |
| /*include_tx_power_level=*/false); |
| |
| // Get to a started state. |
| this->advertiser()->StartAdvertising( |
| addr, ad, scan_data, options, nullptr, this->MakeExpectSuccessCallback()); |
| this->RunUntilIdle(); |
| EXPECT_TRUE(this->GetLastStatus()); |
| EXPECT_TRUE(this->GetControllerAdvertisingState().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 = this->GetControllerAdvertisingState().enabled; |
| if (!was_disabled && !enabled) { |
| was_disabled = true; |
| |
| // Starting now should cancel the stop sequence and succeed. |
| this->advertiser()->StartAdvertising(addr, |
| ad, |
| scan_data, |
| options, |
| nullptr, |
| this->MakeExpectSuccessCallback()); |
| } |
| }; |
| this->test_device()->set_advertising_state_callback(adv_state_cb); |
| |
| this->advertiser()->StopAdvertising(addr); |
| |
| // Advertising should have been momentarily disabled. |
| this->RunUntilIdle(); |
| EXPECT_TRUE(was_disabled); |
| EXPECT_TRUE(enabled); |
| EXPECT_TRUE(this->GetControllerAdvertisingState().enabled); |
| |
| this->test_device()->set_advertising_state_callback(nullptr); |
| } |
| |
| TYPED_TEST(LowEnergyAdvertiserTest, StopWhileStarting) { |
| AdvertisingData ad = this->GetExampleData(); |
| AdvertisingData scan_data = this->GetExampleData(); |
| AdvertisingOptions options(kTestInterval, |
| /*anonymous=*/false, |
| kDefaultNoAdvFlags, |
| /*include_tx_power_level=*/false); |
| |
| this->advertiser()->StartAdvertising(kPublicAddress, |
| ad, |
| scan_data, |
| options, |
| nullptr, |
| this->MakeExpectErrorCallback()); |
| this->advertiser()->StopAdvertising(); |
| |
| this->RunUntilIdle(); |
| EXPECT_TRUE(this->GetLastStatus()); |
| EXPECT_FALSE(this->GetControllerAdvertisingState().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) |
| TYPED_TEST(LowEnergyAdvertiserTest, StopAdvertisingConditions) { |
| AdvertisingData ad = this->GetExampleData(); |
| AdvertisingData scan_data = this->GetExampleData(); |
| AdvertisingOptions options(kTestInterval, |
| /*anonymous=*/false, |
| kDefaultNoAdvFlags, |
| /*include_tx_power_level=*/false); |
| |
| this->advertiser()->StartAdvertising(kRandomAddress, |
| ad, |
| scan_data, |
| options, |
| nullptr, |
| this->MakeExpectSuccessCallback()); |
| |
| this->RunUntilIdle(); |
| |
| EXPECT_TRUE(this->GetLastStatus()); |
| |
| EXPECT_TRUE(this->GetControllerAdvertisingState().enabled); |
| DynamicByteBuffer expected_ad(ad.CalculateBlockSize(/*include_flags=*/true)); |
| ad.WriteBlock(&expected_ad, kDefaultNoAdvFlags); |
| EXPECT_TRUE(ContainersEqual( |
| this->GetControllerAdvertisingState().advertised_view(), expected_ad)); |
| |
| this->RunUntilIdle(); |
| this->advertiser()->StopAdvertising(kPublicAddress); |
| EXPECT_TRUE(this->GetControllerAdvertisingState().enabled); |
| EXPECT_TRUE(ContainersEqual( |
| this->GetControllerAdvertisingState().advertised_view(), expected_ad)); |
| |
| this->advertiser()->StopAdvertising(kRandomAddress); |
| |
| this->RunUntilIdle(); |
| EXPECT_FALSE(this->GetControllerAdvertisingState().enabled); |
| EXPECT_EQ(0u, this->GetControllerAdvertisingState().advertised_view().size()); |
| EXPECT_EQ(0u, this->GetControllerAdvertisingState().scan_rsp_view().size()); |
| } |
| |
| // - Updates data and params for the same address when advertising already |
| TYPED_TEST(LowEnergyAdvertiserTest, AdvertiseUpdate) { |
| AdvertisingData ad = this->GetExampleData(); |
| AdvertisingData scan_data = this->GetExampleData(); |
| AdvertisingOptions options(kTestInterval, |
| /*anonymous=*/false, |
| kDefaultNoAdvFlags, |
| /*include_tx_power_level=*/false); |
| |
| this->advertiser()->StartAdvertising(kRandomAddress, |
| ad, |
| scan_data, |
| options, |
| nullptr, |
| this->MakeExpectSuccessCallback()); |
| this->RunUntilIdle(); |
| |
| EXPECT_TRUE(this->GetLastStatus()); |
| EXPECT_TRUE(this->GetControllerAdvertisingState().enabled); |
| |
| // The expected advertising data payload, with the flags. |
| DynamicByteBuffer expected_ad(ad.CalculateBlockSize(/*include_flags=*/true)); |
| ad.WriteBlock(&expected_ad, kDefaultNoAdvFlags); |
| EXPECT_TRUE(ContainersEqual( |
| this->GetControllerAdvertisingState().advertised_view(), expected_ad)); |
| |
| EXPECT_EQ(kTestInterval.min(), |
| this->GetControllerAdvertisingState().interval_min); |
| EXPECT_EQ(kTestInterval.max(), |
| this->GetControllerAdvertisingState().interval_max); |
| |
| uint16_t new_appearance = 0x6789; |
| ad.SetAppearance(new_appearance); |
| |
| const AdvertisingIntervalRange new_interval(kTestInterval.min() + 1, |
| kTestInterval.max() - 1); |
| AdvertisingOptions new_options(new_interval, |
| /*anonymous=*/false, |
| kDefaultNoAdvFlags, |
| /*include_tx_power_level=*/false); |
| this->advertiser()->StartAdvertising(kRandomAddress, |
| ad, |
| scan_data, |
| new_options, |
| nullptr, |
| this->MakeExpectSuccessCallback()); |
| this->RunUntilIdle(); |
| |
| EXPECT_TRUE(this->GetLastStatus()); |
| EXPECT_TRUE(this->GetControllerAdvertisingState().enabled); |
| |
| DynamicByteBuffer expected_new_ad( |
| ad.CalculateBlockSize(/*include_flags=*/true)); |
| ad.WriteBlock(&expected_new_ad, kDefaultNoAdvFlags); |
| EXPECT_TRUE( |
| ContainersEqual(this->GetControllerAdvertisingState().advertised_view(), |
| expected_new_ad)); |
| |
| EXPECT_EQ(new_interval.min(), |
| this->GetControllerAdvertisingState().interval_min); |
| EXPECT_EQ(new_interval.max(), |
| this->GetControllerAdvertisingState().interval_max); |
| } |
| |
| // Ensures advertising set data is removed from controller memory after |
| // advertising is stopped |
| TYPED_TEST(LowEnergyAdvertiserTest, StopAdvertisingSingleAdvertisement) { |
| AdvertisingData ad = this->GetExampleData(); |
| AdvertisingData scan_data = this->GetExampleData(); |
| |
| // start public address advertising |
| AdvertisingOptions options(kTestInterval, |
| /*anonymous=*/false, |
| kDefaultNoAdvFlags, |
| /*include_tx_power_level=*/false); |
| this->advertiser()->StartAdvertising(kPublicAddress, |
| ad, |
| scan_data, |
| options, |
| nullptr, |
| this->MakeExpectSuccessCallback()); |
| this->RunUntilIdle(); |
| EXPECT_TRUE(this->GetLastStatus()); |
| |
| constexpr uint8_t blank[hci_spec::kMaxLEAdvertisingDataLength] = {0}; |
| |
| // check that advertiser and controller both report the same advertising state |
| EXPECT_TRUE(this->advertiser()->IsAdvertising()); |
| EXPECT_TRUE(this->advertiser()->IsAdvertising(kPublicAddress)); |
| |
| { |
| const FakeController::LEAdvertisingState& state = |
| this->GetControllerAdvertisingState(); |
| EXPECT_TRUE(state.enabled); |
| EXPECT_NE( |
| 0, |
| std::memcmp(blank, state.data, hci_spec::kMaxLEAdvertisingDataLength)); |
| EXPECT_NE(0, state.data_length); |
| EXPECT_NE( |
| 0, |
| std::memcmp(blank, state.data, hci_spec::kMaxLEAdvertisingDataLength)); |
| EXPECT_NE(0, state.scan_rsp_length); |
| } |
| |
| // stop advertising the random address |
| this->advertiser()->StopAdvertising(kPublicAddress); |
| this->RunUntilIdle(); |
| |
| // check that advertiser and controller both report the same advertising state |
| EXPECT_FALSE(this->advertiser()->IsAdvertising()); |
| EXPECT_FALSE(this->advertiser()->IsAdvertising(kPublicAddress)); |
| |
| { |
| const FakeController::LEAdvertisingState& state = |
| this->GetControllerAdvertisingState(); |
| EXPECT_FALSE(state.enabled); |
| EXPECT_EQ( |
| 0, |
| std::memcmp(blank, state.data, hci_spec::kMaxLEAdvertisingDataLength)); |
| EXPECT_EQ(0, state.data_length); |
| EXPECT_EQ( |
| 0, |
| std::memcmp(blank, state.data, hci_spec::kMaxLEAdvertisingDataLength)); |
| EXPECT_EQ(0, state.scan_rsp_length); |
| } |
| } |
| |
| // - Rejects anonymous advertisement (unsupported) |
| TYPED_TEST(LowEnergyAdvertiserTest, NoAnonymous) { |
| AdvertisingData ad = this->GetExampleData(); |
| AdvertisingData scan_data = this->GetExampleData(); |
| AdvertisingOptions options(kTestInterval, |
| /*anonymous=*/true, |
| kDefaultNoAdvFlags, |
| /*include_tx_power_level=*/false); |
| |
| this->advertiser()->StartAdvertising(kRandomAddress, |
| ad, |
| scan_data, |
| options, |
| nullptr, |
| this->MakeExpectErrorCallback()); |
| EXPECT_TRUE(this->GetLastStatus()); |
| EXPECT_FALSE(this->GetControllerAdvertisingState().enabled); |
| } |
| |
| TYPED_TEST(LowEnergyAdvertiserTest, AdvertisingDataTooLong) { |
| AdvertisingData invalid_ad = |
| this->GetTooLargeExampleData(/*include_flags=*/true); |
| AdvertisingData valid_scan_rsp = |
| this->GetExampleData(/*include_flags=*/false); |
| AdvertisingOptions options(kTestInterval, |
| /*anonymous=*/false, |
| kDefaultNoAdvFlags, |
| /*include_tx_power_level=*/false); |
| |
| // Advertising data too large. |
| this->advertiser()->StartAdvertising(kRandomAddress, |
| invalid_ad, |
| valid_scan_rsp, |
| options, |
| nullptr, |
| this->MakeExpectErrorCallback()); |
| this->RunUntilIdle(); |
| auto status = this->GetLastStatus(); |
| ASSERT_TRUE(status); |
| EXPECT_EQ(ToResult(HostError::kAdvertisingDataTooLong), *status); |
| } |
| |
| TYPED_TEST(LowEnergyAdvertiserTest, AdvertisingDataTooLongWithTxPower) { |
| AdvertisingData invalid_ad = this->GetTooLargeExampleData( |
| /*include_flags=*/true, |
| hci_spec::kMaxLEAdvertisingDataLength - kTLVTxPowerLevelSize + 1); |
| AdvertisingData valid_scan_rsp = |
| this->GetExampleData(/*include_flags=*/false); |
| AdvertisingOptions options(kTestInterval, |
| /*anonymous=*/false, |
| kDefaultNoAdvFlags, |
| /*include_tx_power_level=*/true); |
| |
| // Advertising data too large. |
| this->advertiser()->StartAdvertising(kRandomAddress, |
| invalid_ad, |
| valid_scan_rsp, |
| options, |
| nullptr, |
| this->MakeExpectErrorCallback()); |
| this->RunUntilIdle(); |
| auto status = this->GetLastStatus(); |
| ASSERT_TRUE(status); |
| EXPECT_EQ(ToResult(HostError::kAdvertisingDataTooLong), *status); |
| } |
| |
| TYPED_TEST(LowEnergyAdvertiserTest, ScanResponseTooLong) { |
| AdvertisingData valid_ad = this->GetExampleData(); |
| AdvertisingData invalid_scan_rsp = |
| this->GetTooLargeExampleData(/*include_flags=*/false); |
| AdvertisingOptions options(kTestInterval, |
| /*anonymous=*/false, |
| kDefaultNoAdvFlags, |
| /*include_tx_power_level=*/false); |
| this->advertiser()->StartAdvertising(kRandomAddress, |
| valid_ad, |
| invalid_scan_rsp, |
| options, |
| nullptr, |
| this->MakeExpectErrorCallback()); |
| this->RunUntilIdle(); |
| auto status = this->GetLastStatus(); |
| ASSERT_TRUE(status); |
| EXPECT_EQ(ToResult(HostError::kScanResponseTooLong), *status); |
| } |
| |
| TYPED_TEST(LowEnergyAdvertiserTest, ScanResponseTooLongWithTxPower) { |
| AdvertisingData valid_ad = this->GetExampleData(); |
| AdvertisingData invalid_scan_rsp = this->GetTooLargeExampleData( |
| /*include_flags=*/false, |
| hci_spec::kMaxLEAdvertisingDataLength - kTLVTxPowerLevelSize + 1); |
| AdvertisingOptions options(kTestInterval, |
| /*anonymous=*/false, |
| kDefaultNoAdvFlags, |
| /*include_tx_power_level=*/true); |
| this->advertiser()->StartAdvertising(kRandomAddress, |
| valid_ad, |
| invalid_scan_rsp, |
| options, |
| nullptr, |
| this->MakeExpectErrorCallback()); |
| this->RunUntilIdle(); |
| auto status = this->GetLastStatus(); |
| ASSERT_TRUE(status); |
| EXPECT_EQ(ToResult(HostError::kScanResponseTooLong), *status); |
| } |
| |
| TYPED_TEST(LowEnergyAdvertiserTest, |
| DestroyingTransportBeforeAdvertiserDoesNotCrash) { |
| this->DeleteTransport(); |
| this->DestroyAdvertiser(); |
| } |
| |
| } // namespace |
| } // namespace bt::hci |