// 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
