Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c6a6ea8e authored by Henri Chataing's avatar Henri Chataing
Browse files

gd: Fix reporting issues in le_scanning_manager.cc

- Anonymous advertising reports are not being defragmented.
  These events can relay up to 1650 bytes of data.

- Scan response for the extended scan api used with the filter
  accept list filtering are ignored and never reported.

- The advertising SID is not used as it should to disambiguate
  between advertising reports with the same advertising
  address.

- Extended advertising reports that are fragmented in the middle
  of a GAP data entry are not correctly reassembled.
  (PDL parsing will fail in this case)

Bug: 271589688
Test: atest LeScanningReassembler
Change-Id: I991fe8eadcd034541bcaba484bb8c2ad4b97997d
parent 7bfd79ae
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ filegroup {
        "le_address_manager.cc",
        "le_advertising_manager.cc",
        "le_scanning_manager.cc",
        "le_scanning_reassembler.cc",
        "link_key.cc",
        "msft.cc",
        "remote_name_request.cc",
@@ -67,6 +68,7 @@ filegroup {
        "uuid_unittest.cc",
        "le_periodic_sync_manager_test.cc",
        "le_scanning_manager_test.cc",
        "le_scanning_reassembler_test.cc",
        "le_advertising_manager_test.cc",
        "le_address_manager_test.cc",
    ],
+1 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ source_set("BluetoothHciSources") {
    "le_address_manager.cc",
    "le_advertising_manager.cc",
    "le_scanning_manager.cc",
    "le_scanning_reassembler.cc",
    "link_key.cc",
    "msft.cc",
    "remote_name_request.cc",
+50 −176
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@
#include "hci/hci_packets.h"
#include "hci/le_periodic_sync_manager.h"
#include "hci/le_scanning_interface.h"
#include "hci/le_scanning_reassembler.h"
#include "hci/vendor_specific_event_manager.h"
#include "module.h"
#include "os/handler.h"
@@ -61,80 +62,6 @@ struct Scanner {
  bool in_use;
};

class AdvertisingCache {
 public:
  const std::vector<uint8_t>& Set(const AddressWithType& address_with_type, std::vector<uint8_t> data) {
    auto it = Find(address_with_type);
    if (it != items.end()) {
      it->data = std::move(data);
      return it->data;
    }

    if (items.size() > cache_max) {
      items.pop_back();
    }

    items.emplace_front(address_with_type, std::move(data));
    return items.front().data;
  }

  bool Exist(const AddressWithType& address_with_type) {
    auto it = Find(address_with_type);
    if (it == items.end()) {
      return false;
    }
    return true;
  }

  const std::vector<uint8_t>& Append(const AddressWithType& address_with_type, std::vector<uint8_t> data) {
    auto it = Find(address_with_type);
    if (it != items.end()) {
      it->data.insert(it->data.end(), data.begin(), data.end());
      return it->data;
    }

    if (items.size() > cache_max) {
      items.pop_back();
    }

    items.emplace_front(address_with_type, std::move(data));
    return items.front().data;
  }

  /* Clear data for device |addr_type, addr| */
  void Clear(AddressWithType address_with_type) {
    auto it = Find(address_with_type);
    if (it != items.end()) {
      items.erase(it);
    }
  }

  void ClearAll() {
    items.clear();
  }

  struct Item {
    AddressWithType address_with_type;
    std::vector<uint8_t> data;

    Item(const AddressWithType& address_with_type, std::vector<uint8_t> data)
        : address_with_type(address_with_type), data(data) {}
  };

  std::list<Item>::iterator Find(const AddressWithType& address_with_type) {
    for (auto it = items.begin(); it != items.end(); it++) {
      if (it->address_with_type == address_with_type) {
        return it;
      }
    }
    return items.end();
  }

  /* we keep maximum 7 devices in the cache */
  const size_t cache_max = 1000;
  std::list<Item> items;
};

class NullScanningCallback : public ScanningCallback {
  void OnScannerRegistered(const Uuid app_uuid, ScannerId scanner_id, ScanningStatus status) override {
    LOG_INFO("OnScannerRegistered in NullScanningCallback");
@@ -305,13 +232,13 @@ struct LeScanningManager::impl : public LeAddressManagerCallback {
  void handle_scan_results(LeMetaEventView event) {
    switch (event.GetSubeventCode()) {
      case SubeventCode::ADVERTISING_REPORT:
        handle_advertising_report(LeAdvertisingReportView::Create(event));
        handle_advertising_report(LeAdvertisingReportRawView::Create(event));
        break;
      case SubeventCode::DIRECTED_ADVERTISING_REPORT:
        handle_directed_advertising_report(LeDirectedAdvertisingReportView::Create(event));
        break;
      case SubeventCode::EXTENDED_ADVERTISING_REPORT:
        handle_extended_advertising_report(LeExtendedAdvertisingReportView::Create(event));
        handle_extended_advertising_report(LeExtendedAdvertisingReportRawView::Create(event));
        break;
      case SubeventCode::PERIODIC_ADVERTISING_SYNC_ESTABLISHED:
        LePeriodicAdvertisingSyncEstablishedView::Create(event);
@@ -350,45 +277,45 @@ struct LeScanningManager::impl : public LeAddressManagerCallback {
    bool truncated{false};
  };

  void transform_to_extended_event_type(uint16_t* extended_event_type, ExtendedEventTypeOptions o) {
    ASSERT(extended_event_type != nullptr);
    *extended_event_type = (o.connectable ? 0x0001 << 0 : 0) | (o.scannable ? 0x0001 << 1 : 0) |
  uint16_t transform_to_extended_event_type(ExtendedEventTypeOptions o) {
    return (o.connectable ? 0x0001 << 0 : 0) | (o.scannable ? 0x0001 << 1 : 0) |
           (o.directed ? 0x0001 << 2 : 0) | (o.scan_response ? 0x0001 << 3 : 0) |
           (o.legacy ? 0x0001 << 4 : 0) | (o.continuing ? 0x0001 << 5 : 0) |
           (o.truncated ? 0x0001 << 6 : 0);
  }

  void handle_advertising_report(LeAdvertisingReportView event_view) {
  void handle_advertising_report(LeAdvertisingReportRawView event_view) {
    if (!event_view.IsValid()) {
      LOG_INFO("Dropping invalid advertising event");
      return;
    }
    std::vector<LeAdvertisingResponse> reports = event_view.GetResponses();
    std::vector<LeAdvertisingResponseRaw> reports = event_view.GetResponses();
    if (reports.empty()) {
      LOG_INFO("Zero results in advertising event");
      return;
    }

    for (LeAdvertisingResponse report : reports) {
    for (LeAdvertisingResponseRaw report : reports) {
      uint16_t extended_event_type = 0;
      switch (report.event_type_) {
        case AdvertisingEventType::ADV_IND:
          transform_to_extended_event_type(
              &extended_event_type, {.connectable = true, .scannable = true, .legacy = true});
          extended_event_type = transform_to_extended_event_type(
              {.connectable = true, .scannable = true, .legacy = true});
          break;
        case AdvertisingEventType::ADV_DIRECT_IND:
          transform_to_extended_event_type(
              &extended_event_type, {.connectable = true, .directed = true, .legacy = true});
          extended_event_type = transform_to_extended_event_type(
              {.connectable = true, .directed = true, .legacy = true});
          break;
        case AdvertisingEventType::ADV_SCAN_IND:
          transform_to_extended_event_type(&extended_event_type, {.scannable = true, .legacy = true});
          extended_event_type =
              transform_to_extended_event_type({.scannable = true, .legacy = true});
          break;
        case AdvertisingEventType::ADV_NONCONN_IND:
          transform_to_extended_event_type(&extended_event_type, {.legacy = true});
          extended_event_type = transform_to_extended_event_type({.legacy = true});
          break;
        case AdvertisingEventType::SCAN_RESPONSE:
          transform_to_extended_event_type(
              &extended_event_type, {.connectable = true, .scannable = true, .scan_response = true, .legacy = true});
          extended_event_type = transform_to_extended_event_type(
              {.connectable = true, .scannable = true, .scan_response = true, .legacy = true});
          break;
        default:
          LOG_WARN("Unsupported event type:%d", (uint16_t)report.event_type_);
@@ -409,33 +336,23 @@ struct LeScanningManager::impl : public LeAddressManagerCallback {
    }
  }

  void handle_directed_advertising_report(LeDirectedAdvertisingReportView event_view) {
    if (!event_view.IsValid()) {
      LOG_INFO("Dropping invalid advertising event");
      return;
    }
    std::vector<LeDirectedAdvertisingResponse> reports = event_view.GetResponses();
    if (reports.empty()) {
      LOG_INFO("Zero results in advertising event");
      return;
    }
    uint16_t extended_event_type = 0;
    transform_to_extended_event_type(&extended_event_type, {.connectable = true, .directed = true, .legacy = true});
    // TODO: parse report
  void handle_directed_advertising_report(LeDirectedAdvertisingReportView /*event_view*/) {
    LOG_WARN("HCI Directed Advertising Report events are not supported");
  }

  void handle_extended_advertising_report(LeExtendedAdvertisingReportView event_view) {
  void handle_extended_advertising_report(LeExtendedAdvertisingReportRawView event_view) {
    if (!event_view.IsValid()) {
      LOG_INFO("Dropping invalid advertising event");
      return;
    }
    std::vector<LeExtendedAdvertisingResponse> reports = event_view.GetResponses();

    std::vector<LeExtendedAdvertisingResponseRaw> reports = event_view.GetResponses();
    if (reports.empty()) {
      LOG_INFO("Zero results in advertising event");
      return;
    }

    for (LeExtendedAdvertisingResponse report : reports) {
    for (LeExtendedAdvertisingResponseRaw& report : reports) {
      uint16_t event_type = report.connectable_ | (report.scannable_ << kScannableBit) |
                            (report.directed_ << kDirectedBit) | (report.scan_response_ << kScanResponseBit) |
                            (report.legacy_ << kLegacyBit) | ((uint16_t)report.data_status_ << kDataStatusBits);
@@ -463,62 +380,19 @@ struct LeScanningManager::impl : public LeAddressManagerCallback {
      int8_t tx_power,
      int8_t rssi,
      uint16_t periodic_advertising_interval,
      std::vector<LengthAndData> advertising_data) {
    bool is_scannable = event_type & (1 << kScannableBit);
    bool is_scan_response = event_type & (1 << kScanResponseBit);
    bool is_legacy = event_type & (1 << kLegacyBit);

    auto significant_data = std::vector<uint8_t>{};
    for (const auto& datum : advertising_data) {
      if (!datum.data_.empty()) {
        significant_data.push_back(static_cast<uint8_t>(datum.data_.size()));
        significant_data.insert(significant_data.end(), datum.data_.begin(), datum.data_.end());
      }
    }

    if (address_type == (uint8_t)DirectAdvertisingAddressType::NO_ADDRESS_PROVIDED) {
      scanning_callbacks_->OnScanResult(
          event_type,
          address_type,
          address,
          primary_phy,
          secondary_phy,
          advertising_sid,
          tx_power,
          rssi,
          periodic_advertising_interval,
          significant_data);
      return;
    } else if (address == Address::kEmpty) {
      LOG_WARN("Receive non-anonymous advertising report with empty address, skip!");
      return;
    }

    AddressWithType address_with_type(address, (AddressType)address_type);

    if (is_legacy && is_scan_response && !advertising_cache_.Exist(address_with_type)) {
      return;
    }

    bool is_start = is_legacy && is_scannable && !is_scan_response;

    std::vector<uint8_t> const& adv_data = is_start ? advertising_cache_.Set(address_with_type, significant_data)
                                                    : advertising_cache_.Append(address_with_type, significant_data);

    uint8_t data_status = event_type >> kDataStatusBits;
    if (data_status == (uint8_t)DataStatus::CONTINUING) {
      // Waiting for whole data
      return;
    }

    // Don't wait for scan response if it's a filtered scan since the filter might miss the scan
    // response.
    if (is_scannable && !is_scan_response &&
        filter_policy_ != LeScanningFilterPolicy::FILTER_ACCEPT_LIST_ONLY) {
      // Waiting for scan response
      return;
    }

      const std::vector<uint8_t>& advertising_data) {
    // When using the vendor command Le Set Extended Params to
    // configure a filter accept list based e.g. on the service UUIDs
    // found in the report, we ignore the scan responses as we cannot be
    // certain that they will not be dropped by the filter.
    scanning_reassembler_.SetIgnoreScanResponses(
        filter_policy_ == LeScanningFilterPolicy::FILTER_ACCEPT_LIST_ONLY &&
        api_type_ == ScanApiType::ANDROID_HCI);

    auto complete_advertising_data = scanning_reassembler_.ProcessAdvertisingReport(
        event_type, address_type, address, advertising_sid, advertising_data);

    if (complete_advertising_data.has_value()) {
      switch (address_type) {
        case (uint8_t)AddressType::PUBLIC_DEVICE_ADDRESS:
        case (uint8_t)AddressType::PUBLIC_IDENTITY_ADDRESS:
@@ -529,6 +403,7 @@ struct LeScanningManager::impl : public LeAddressManagerCallback {
          address_type = (uint8_t)AddressType::RANDOM_DEVICE_ADDRESS;
          break;
      }

      scanning_callbacks_->OnScanResult(
          event_type,
          address_type,
@@ -539,9 +414,8 @@ struct LeScanningManager::impl : public LeAddressManagerCallback {
          tx_power,
          rssi,
          periodic_advertising_interval,
        adv_data);

    advertising_cache_.Clear(address_with_type);
          complete_advertising_data.value());
    }
  }

  void configure_scan() {
@@ -1692,7 +1566,7 @@ struct LeScanningManager::impl : public LeAddressManagerCallback {
  bool is_scanning_ = false;
  bool scan_on_resume_ = false;
  bool paused_ = false;
  AdvertisingCache advertising_cache_;
  LeScanningReassembler scanning_reassembler_;
  bool is_filter_supported_ = false;
  bool is_ad_type_filter_supported_ = false;
  bool is_batch_scan_supported_ = false;
+179 −0
Original line number Diff line number Diff line
/*
 * Copyright 2023 The Android Open Source Project
 *
 * 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
 *
 *      http://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 "hci/le_scanning_reassembler.h"

#include <memory>
#include <unordered_map>

#include "hci/acl_manager.h"
#include "hci/controller.h"
#include "hci/hci_layer.h"
#include "hci/hci_packets.h"
#include "hci/le_periodic_sync_manager.h"
#include "hci/le_scanning_interface.h"
#include "hci/vendor_specific_event_manager.h"
#include "module.h"
#include "os/handler.h"
#include "os/log.h"
#include "storage/storage_module.h"

namespace bluetooth::hci {

std::optional<std::vector<uint8_t>> LeScanningReassembler::ProcessAdvertisingReport(
    uint16_t event_type,
    uint8_t address_type,
    Address address,
    uint8_t advertising_sid,
    const std::vector<uint8_t>& advertising_data) {
  bool is_scannable = event_type & (1 << kScannableBit);
  bool is_scan_response = event_type & (1 << kScanResponseBit);
  bool is_legacy = event_type & (1 << kLegacyBit);
  DataStatus data_status = DataStatus((event_type >> kDataStatusBits) & 0x3);

  if (address_type != (uint8_t)DirectAdvertisingAddressType::NO_ADDRESS_PROVIDED &&
      address == Address::kEmpty) {
    LOG_WARN("Ignoring non-anonymous advertising report with empty address");
    return {};
  }

  AdvertisingKey key(address, DirectAdvertisingAddressType(address_type), advertising_sid);

  // Ignore scan responses received without a matching advertising event.
  if (is_scan_response && (ignore_scan_responses_ || !ContainsFragment(key))) {
    LOG_INFO("Ignoring scan response received without advertising event");
    return {};
  }

  // Legacy advertising is always complete, we can drop
  // the previous data as safety measure if the report is not a scan
  // response.
  if (is_legacy && !is_scan_response) {
    LOG_DEBUG("Dropping repeated legacy advertising data");
    RemoveFragment(key);
  }

  // Concatenate the data with existing fragments.
  std::list<AdvertisingFragment>::iterator advertising_fragment =
      AppendFragment(key, advertising_data);

  // Trim the advertising data when the complete payload is received.
  if (data_status != DataStatus::CONTINUING) {
    advertising_fragment->data = TrimAdvertisingData(advertising_fragment->data);
  }

  // TODO(b/272120114) waiting for a scan response here is prone to failure as the
  // SCAN_REQ PDUs can be rejected by the advertiser according to the
  // advertising filter parameter.
  bool expect_scan_response = is_scannable && !is_scan_response && !ignore_scan_responses_;

  // Check if we should wait for additional fragments:
  // - For legacy advertising, when a scan response is expected.
  // - For extended advertising, when the current data is marked
  //   incomplete OR when a scan response is expected.
  if (data_status == DataStatus::CONTINUING || expect_scan_response) {
    return {};
  }

  // Otherwise the full advertising report has been reassembled,
  // removed the cache entry and return the complete advertising data.
  std::vector<uint8_t> complete_advertising_data = std::move(advertising_fragment->data);
  cache_.erase(advertising_fragment);
  return complete_advertising_data;
}

/// Trim the advertising data by removing empty or overflowing
/// GAP Data entries.
std::vector<uint8_t> LeScanningReassembler::TrimAdvertisingData(
    const std::vector<uint8_t>& advertising_data) {
  // Remove empty and overflowing entries from the advertising data.
  std::vector<uint8_t> significant_advertising_data;
  for (size_t offset = 0; offset < advertising_data.size();) {
    size_t remaining_size = advertising_data.size() - offset;
    uint8_t entry_size = advertising_data[offset];

    if (entry_size != 0 && entry_size < remaining_size) {
      significant_advertising_data.push_back(entry_size);
      significant_advertising_data.insert(
          significant_advertising_data.end(),
          advertising_data.begin() + offset + 1,
          advertising_data.begin() + offset + 1 + entry_size);
    }

    offset += entry_size + 1;
  }

  return significant_advertising_data;
}

LeScanningReassembler::AdvertisingKey::AdvertisingKey(
    Address address, DirectAdvertisingAddressType address_type, uint8_t sid)
    : address(), sid() {
  // The address type is NO_ADDRESS_PROVIDED for anonymous advertising.
  if (address_type != DirectAdvertisingAddressType::NO_ADDRESS_PROVIDED) {
    this->address = AddressWithType(address, AddressType(address_type));
  }
  // 0xff is reserved to indicate that the ADI field was not present
  // in the ADV_EXT_IND PDU.
  if (sid != 0xff) {
    this->sid = sid;
  }
}

bool LeScanningReassembler::AdvertisingKey::operator==(const AdvertisingKey& other) {
  return address == other.address && sid == other.sid;
}

/// Append to the current advertising data of the selected advertiser.
/// If the advertiser is unknown a new entry is added, optionally by
/// dropping the oldest advertiser.
std::list<LeScanningReassembler::AdvertisingFragment>::iterator
LeScanningReassembler::AppendFragment(const AdvertisingKey& key, const std::vector<uint8_t>& data) {
  auto it = FindFragment(key);
  if (it != cache_.end()) {
    it->data.insert(it->data.end(), data.cbegin(), data.cend());
    return it;
  }

  if (cache_.size() > kMaximumCacheSize) {
    cache_.pop_back();
  }

  cache_.emplace_front(key, data);
  return cache_.begin();
}

void LeScanningReassembler::RemoveFragment(const AdvertisingKey& key) {
  auto it = FindFragment(key);
  if (it != cache_.end()) {
    cache_.erase(it);
  }
}

bool LeScanningReassembler::ContainsFragment(const AdvertisingKey& key) {
  return FindFragment(key) != cache_.end();
}

std::list<LeScanningReassembler::AdvertisingFragment>::iterator LeScanningReassembler::FindFragment(
    const AdvertisingKey& key) {
  for (auto it = cache_.begin(); it != cache_.end(); it++) {
    if (it->key == key) {
      return it;
    }
  }
  return cache_.end();
}

}  // namespace bluetooth::hci
+120 −0
Original line number Diff line number Diff line
/*
 * Copyright 2019 The Android Open Source Project
 *
 * 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
 *
 *      http://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.
 */
#pragma once

#include <gtest/gtest_prod.h>

#include <cstdint>
#include <list>
#include <optional>
#include <vector>

#include "hci/address_with_type.h"
#include "hci/hci_packets.h"

namespace bluetooth::hci {

/// The LE Scanning reassembler is responsible for defragmenting
/// LE advertising reports that are too large to fit inside an HCI event
/// and were fragmented by the compiler.
/// The reassembler also joins scan response data with the
/// matching advertising data.

class LeScanningReassembler {
 public:
  LeScanningReassembler(){};
  LeScanningReassembler(const LeScanningReassembler&) = delete;
  LeScanningReassembler& operator=(const LeScanningReassembler&) = delete;

  /// Process an incoming advertsing report, extracted from any of the
  /// HCI LE Advertising Report or the HCI LE Extended Advertising Report
  /// events.
  /// Returns the completed advertising data if the event was complete, or the
  /// completion of a fragmented advertising event.
  std::optional<std::vector<uint8_t>> ProcessAdvertisingReport(
      uint16_t event_type,
      uint8_t address_type,
      Address address,
      uint8_t advertising_sid,
      const std::vector<uint8_t>& advertising_data);

  /// Configure the scan response filter.
  /// If true all scan responses are ignored.
  void SetIgnoreScanResponses(bool ignore_scan_responses) {
    ignore_scan_responses_ = ignore_scan_responses;
  }

 private:
  /// Determine if scan responses should be processed or ignored.
  bool ignore_scan_responses_{false};

  /// Constants for parsing event_type.
  static constexpr uint8_t kScannableBit = 1;
  static constexpr uint8_t kDirectedBit = 2;
  static constexpr uint8_t kScanResponseBit = 3;
  static constexpr uint8_t kLegacyBit = 4;
  static constexpr uint8_t kDataStatusBits = 5;

  /// Packs the information necessary to disambiguate advertising events:
  /// - For legacy advertising events, the advertising address and
  ///   advertising address type are used to disambiguate advertisers.
  /// - For extended advertising events, the SID is optionally used to
  ///   differentiate between advertising sets of the same advertiser.
  ///   The advertiser can also be anonymous in which case
  ///   the address is not provided. In this case, and when the SID
  ///   is missing, we trust the controller to send fragments of the same
  ///   advertisement together and not interleaved with that of other
  ///   advertisers.
  struct AdvertisingKey {
    std::optional<AddressWithType> address;
    std::optional<uint8_t> sid;

    AdvertisingKey(Address address, DirectAdvertisingAddressType address_type, uint8_t sid);
    bool operator==(const AdvertisingKey& other);
  };

  /// Packs incomplete advertising data.
  struct AdvertisingFragment {
    AdvertisingKey key;
    std::vector<uint8_t> data;

    AdvertisingFragment(const AdvertisingKey& key, const std::vector<uint8_t>& data)
        : key(key), data(data.begin(), data.end()) {}
  };

  /// Advertising cache for de-fragmenting extended advertising reports,
  /// and joining advertising reports with the matching scan response when
  /// applicable.
  /// The cached advertising data is removed as soon as the complete
  /// advertisement is got (including the scan response).
  static constexpr size_t kMaximumCacheSize = 16;
  std::list<AdvertisingFragment> cache_;

  /// Advertising cache management methods.
  std::list<AdvertisingFragment>::iterator AppendFragment(
      const AdvertisingKey& key, const std::vector<uint8_t>& data);
  void RemoveFragment(const AdvertisingKey& key);
  bool ContainsFragment(const AdvertisingKey& key);
  std::list<AdvertisingFragment>::iterator FindFragment(const AdvertisingKey& key);

  /// Trim the advertising data by removing empty or overflowing
  /// GAP Data entries.
  static std::vector<uint8_t> TrimAdvertisingData(const std::vector<uint8_t>& advertising_data);

  FRIEND_TEST(LeScanningReassemblerTest, trim_advertising_data);
};

}  // namespace bluetooth::hci
Loading