| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "legacy_low_energy_advertiser.h" |
| |
| #include <endian.h> |
| #include <lib/async/default.h> |
| #include <zircon/assert.h> |
| |
| #include "src/connectivity/bluetooth/core/bt-host/common/byte_buffer.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/log.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci-spec/util.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci/transport.h" |
| |
| namespace bt::hci { |
| |
| namespace { |
| |
| // The size, in bytes, of the serialized TX Power Level. |
| constexpr size_t kTxPowerLevelTLVSize = 3; |
| |
| // Helpers for building HCI command packets: |
| |
| std::unique_ptr<CommandPacket> BuildEnablePacket(GenericEnableParam enable) { |
| constexpr size_t kPayloadSize = sizeof(LESetAdvertisingEnableCommandParams); |
| auto packet = CommandPacket::New(kLESetAdvertisingEnable, kPayloadSize); |
| packet->mutable_payload<LESetAdvertisingEnableCommandParams>()->advertising_enable = enable; |
| ZX_ASSERT(packet); |
| return packet; |
| } |
| |
| std::unique_ptr<CommandPacket> BuildSetAdvertisingData(const AdvertisingData& data, |
| AdvFlags flags) { |
| auto packet = |
| CommandPacket::New(kLESetAdvertisingData, sizeof(LESetAdvertisingDataCommandParams)); |
| packet->mutable_view()->mutable_payload_data().SetToZeros(); |
| |
| auto params = packet->mutable_payload<LESetAdvertisingDataCommandParams>(); |
| params->adv_data_length = data.CalculateBlockSize(/*include_flags=*/true); |
| |
| MutableBufferView adv_view(params->adv_data, params->adv_data_length); |
| data.WriteBlock(&adv_view, flags); |
| |
| return packet; |
| } |
| |
| std::unique_ptr<CommandPacket> BuildSetScanResponse(const AdvertisingData& scan_rsp) { |
| auto packet = |
| CommandPacket::New(kLESetScanResponseData, sizeof(LESetScanResponseDataCommandParams)); |
| packet->mutable_view()->mutable_payload_data().SetToZeros(); |
| |
| auto params = packet->mutable_payload<LESetScanResponseDataCommandParams>(); |
| params->scan_rsp_data_length = scan_rsp.CalculateBlockSize(); |
| |
| MutableBufferView scan_data_view(params->scan_rsp_data, sizeof(params->scan_rsp_data)); |
| scan_rsp.WriteBlock(&scan_data_view, std::nullopt); |
| |
| return packet; |
| } |
| |
| std::unique_ptr<CommandPacket> BuildSetAdvertisingParams(LEAdvertisingType type, |
| LEOwnAddressType own_address_type, |
| AdvertisingIntervalRange interval) { |
| auto packet = CommandPacket::New(kLESetAdvertisingParameters, |
| sizeof(LESetAdvertisingParametersCommandParams)); |
| packet->mutable_view()->mutable_payload_data().SetToZeros(); |
| |
| auto params = packet->mutable_payload<LESetAdvertisingParametersCommandParams>(); |
| params->adv_interval_min = htole16(interval.min()); |
| params->adv_interval_max = htole16(interval.max()); |
| params->adv_type = type; |
| params->own_address_type = own_address_type; |
| params->adv_channel_map = kLEAdvertisingChannelAll; |
| params->adv_filter_policy = LEAdvFilterPolicy::kAllowAll; |
| |
| // We don't support directed advertising yet, so leave peer_address as 0x00 |
| // (|packet| parameters are initialized to zero above). |
| |
| return packet; |
| } |
| |
| std::unique_ptr<CommandPacket> BuildReadAdvertisingTxPower() { |
| auto packet = CommandPacket::New(kLEReadAdvertisingChannelTxPower); |
| ZX_ASSERT(packet); |
| return packet; |
| } |
| |
| } // namespace |
| |
| LegacyLowEnergyAdvertiser::LegacyLowEnergyAdvertiser(fxl::WeakPtr<Transport> hci) |
| : hci_(std::move(hci)), starting_(false), connect_callback_(nullptr) { |
| hci_cmd_runner_ = std::make_unique<SequentialCommandRunner>(async_get_default_dispatcher(), hci_); |
| } |
| |
| LegacyLowEnergyAdvertiser::~LegacyLowEnergyAdvertiser() { StopAdvertisingInternal(); } |
| |
| size_t LegacyLowEnergyAdvertiser::GetSizeLimit() { return kMaxLEAdvertisingDataLength; } |
| |
| bool LegacyLowEnergyAdvertiser::AllowsRandomAddressChange() const { |
| return !starting_ && !advertising(); |
| } |
| |
| void LegacyLowEnergyAdvertiser::StartAdvertisingInternal( |
| const DeviceAddress& address, const AdvertisingData& data, const AdvertisingData& scan_rsp, |
| AdvertisingIntervalRange interval, AdvFlags flags, ConnectionCallback connect_callback, |
| StatusCallback callback) { |
| if (advertising()) { |
| // Temporarily disable advertising so we can tweak the parameters. |
| hci_cmd_runner_->QueueCommand(BuildEnablePacket(GenericEnableParam::kDisable)); |
| } |
| |
| // Set advertising and scan response data. If either data is empty then it |
| // will be cleared accordingly. |
| hci_cmd_runner_->QueueCommand(BuildSetAdvertisingData(data, flags)); |
| hci_cmd_runner_->QueueCommand(BuildSetScanResponse(scan_rsp)); |
| |
| // Set advertising parameters |
| LEAdvertisingType type = LEAdvertisingType::kAdvNonConnInd; |
| if (connect_callback) { |
| type = LEAdvertisingType::kAdvInd; |
| } else if (scan_rsp.CalculateBlockSize() > 0) { |
| type = LEAdvertisingType::kAdvScanInd; |
| } |
| |
| LEOwnAddressType own_addr_type; |
| if (address.type() == DeviceAddress::Type::kLEPublic) { |
| own_addr_type = LEOwnAddressType::kPublic; |
| } else { |
| own_addr_type = LEOwnAddressType::kRandom; |
| } |
| |
| hci_cmd_runner_->QueueCommand(BuildSetAdvertisingParams(type, own_addr_type, interval)); |
| |
| // Enable advertising. |
| hci_cmd_runner_->QueueCommand(BuildEnablePacket(GenericEnableParam::kEnable)); |
| |
| hci_cmd_runner_->RunCommands( |
| [this, address, callback = std::move(callback), |
| connect_callback = std::move(connect_callback)](Status status) mutable { |
| ZX_DEBUG_ASSERT(starting_); |
| starting_ = false; |
| |
| if (bt_is_error(status, ERROR, "hci-le", "failed to start advertising")) { |
| // Clear out the advertising data if it partially succeeded. |
| StopAdvertisingInternal(); |
| } else { |
| bt_log(INFO, "hci-le", "advertising enabled"); |
| advertised_ = address; |
| connect_callback_ = std::move(connect_callback); |
| } |
| |
| callback(status); |
| }); |
| } |
| |
| void LegacyLowEnergyAdvertiser::StartAdvertising( |
| const DeviceAddress& address, const AdvertisingData& data, const AdvertisingData& scan_rsp, |
| AdvertisingOptions adv_options, ConnectionCallback connect_callback, StatusCallback callback) { |
| ZX_DEBUG_ASSERT(callback); |
| ZX_DEBUG_ASSERT(address.type() != DeviceAddress::Type::kBREDR); |
| |
| if (adv_options.anonymous) { |
| bt_log(DEBUG, "hci-le", "anonymous advertising not supported"); |
| callback(Status(HostError::kNotSupported)); |
| return; |
| } |
| |
| if (advertising()) { |
| if (address != advertised_) { |
| bt_log(DEBUG, "hci-le", "already advertising"); |
| callback(Status(HostError::kNotSupported)); |
| return; |
| } |
| bt_log(DEBUG, "hci-le", "updating existing advertisement"); |
| } |
| |
| // If the TX Power Level is requested, ensure both buffers have enough space. |
| size_t size_limit = GetSizeLimit(); |
| if (adv_options.include_tx_power_level) |
| size_limit -= kTxPowerLevelTLVSize; |
| |
| if (data.CalculateBlockSize(/*include_flags=*/true) > size_limit) { |
| bt_log(DEBUG, "hci-le", "advertising data too large"); |
| callback(Status(HostError::kAdvertisingDataTooLong)); |
| return; |
| } |
| |
| if (scan_rsp.CalculateBlockSize() > size_limit) { |
| bt_log(DEBUG, "hci-le", "scan response too large"); |
| callback(Status(HostError::kScanResponseTooLong)); |
| return; |
| } |
| |
| // Midst of a TX power level read - send a cancel over the previous status callback. |
| if (staged_params_.has_value()) { |
| auto status_cb = std::move(staged_params_.value().callback); |
| status_cb(Status(HostError::kCanceled)); |
| } |
| |
| // If the TX Power level is requested, then stage the parameters for the read operation. |
| // If there already is an outstanding TX Power Level read request, return early. |
| // Advertising on the outstanding call will now use the updated |staged_params_|. |
| if (adv_options.include_tx_power_level) { |
| AdvertisingData data_copy, scan_rsp_copy; |
| data.Copy(&data_copy); |
| scan_rsp.Copy(&scan_rsp_copy); |
| staged_params_ = StagedParams{address, |
| adv_options.interval, |
| adv_options.flags, |
| std::move(data_copy), |
| std::move(scan_rsp_copy), |
| std::move(connect_callback), |
| std::move(callback)}; |
| if (starting_ && hci_cmd_runner_->IsReady()) { |
| return; |
| } |
| } |
| |
| if (!hci_cmd_runner_->IsReady()) { |
| // Abort any remaining commands from the current stop sequence. If we got |
| // here then the controller MUST receive our request to disable advertising, |
| // so the commands that we send next will overwrite the current advertising |
| // settings and re-enable it. |
| hci_cmd_runner_->Cancel(); |
| } |
| |
| starting_ = true; |
| |
| // If the TX Power Level is requested, read it from the controller, update the data buf, and |
| // proceed with starting advertising. |
| // |
| // If advertising was canceled during the TX power level read (either |starting_| was |
| // reset or the |callback| was moved), return early. |
| if (adv_options.include_tx_power_level) { |
| auto power_cb = [this](auto, const hci::EventPacket& event) mutable { |
| ZX_ASSERT(staged_params_.has_value()); |
| if ((!starting_) || (!staged_params_.value().callback)) { |
| bt_log(INFO, "hci-le", "Advertising canceled during TX Power Level read."); |
| return; |
| } |
| |
| if (hci_is_error(event, WARN, "hci-le", "read TX power level failed")) { |
| staged_params_.value().callback(event.ToStatus()); |
| staged_params_ = {}; |
| starting_ = false; |
| return; |
| } |
| |
| const auto& params = event.return_params<hci::LEReadAdvertisingChannelTxPowerReturnParams>(); |
| |
| // Update the advertising and scan response data with the TX power level. |
| auto staged_params = std::move(staged_params_.value()); |
| staged_params.data.SetTxPower(params->tx_power); |
| if (staged_params.scan_rsp.CalculateBlockSize()) { |
| staged_params.scan_rsp.SetTxPower(params->tx_power); |
| } |
| // Reset the |staged_params_| as it is no longer in use. |
| staged_params_ = {}; |
| |
| StartAdvertisingInternal(staged_params.address, staged_params.data, staged_params.scan_rsp, |
| staged_params.interval, staged_params.flags, |
| std::move(staged_params.connect_callback), |
| std::move(staged_params.callback)); |
| }; |
| |
| hci_->command_channel()->SendCommand(BuildReadAdvertisingTxPower(), std::move(power_cb)); |
| return; |
| } |
| |
| StartAdvertisingInternal(address, data, scan_rsp, adv_options.interval, adv_options.flags, |
| std::move(connect_callback), std::move(callback)); |
| } |
| |
| // TODO(fxbug.dev/50542): StopAdvertising() should cancel outstanding calls to StartAdvertising() |
| // and clean up state. |
| bool LegacyLowEnergyAdvertiser::StopAdvertising(const DeviceAddress& address) { |
| if (advertised_ != address) { |
| // not advertising, or not on this address. |
| return false; |
| } |
| StopAdvertisingInternal(); |
| return true; |
| } |
| |
| void LegacyLowEnergyAdvertiser::StopAdvertisingInternal() { |
| connect_callback_ = nullptr; |
| |
| if (!hci_cmd_runner_->IsReady()) { |
| if (!starting_) { |
| bt_log(DEBUG, "hci-le", "already stopping"); |
| |
| // The advertised address must have been cleared in this state. |
| ZX_DEBUG_ASSERT(!advertising()); |
| return; |
| } |
| |
| // Cancel the pending start |
| ZX_DEBUG_ASSERT(starting_); |
| hci_cmd_runner_->Cancel(); |
| starting_ = false; |
| } |
| |
| // Even on failure, we want to consider us not advertising. Clear the |
| // advertised address here so that new advertisements can be requested right |
| // away. |
| advertised_ = {}; |
| |
| // Disable advertising |
| hci_cmd_runner_->QueueCommand(BuildEnablePacket(GenericEnableParam::kDisable)); |
| |
| // Unset advertising data |
| auto data_packet = |
| CommandPacket::New(kLESetAdvertisingData, sizeof(LESetAdvertisingDataCommandParams)); |
| data_packet->mutable_view()->mutable_payload_data().SetToZeros(); |
| hci_cmd_runner_->QueueCommand(std::move(data_packet)); |
| |
| // Set scan response data |
| auto scan_rsp_packet = |
| CommandPacket::New(kLESetScanResponseData, sizeof(LESetScanResponseDataCommandParams)); |
| scan_rsp_packet->mutable_view()->mutable_payload_data().SetToZeros(); |
| hci_cmd_runner_->QueueCommand(std::move(scan_rsp_packet)); |
| |
| hci_cmd_runner_->RunCommands( |
| [](Status status) { bt_log(INFO, "hci-le", "advertising stopped: %s", bt_str(status)); }); |
| } |
| |
| void LegacyLowEnergyAdvertiser::OnIncomingConnection(ConnectionHandle handle, Connection::Role role, |
| const DeviceAddress& peer_address, |
| const LEConnectionParameters& conn_params) { |
| // Immediately construct a Connection object. If this object goes out of scope following the error |
| // checks below, it will send the a command to disconnect the link. We assign |advertised_| as the |
| // local address however this address may be invalid if we're not advertising. This is OK as the |
| // link will be disconnected in that case before it can propagate to higher layers. |
| // |
| // TODO(fxbug.dev/2761): We can't assign the default address since an LE connection cannot have a |
| // BR/EDR type. This temporary default won't be necessary was we remove transport from the address |
| // type. |
| auto local_address = |
| advertising() ? advertised_ : DeviceAddress(DeviceAddress::Type::kLEPublic, {0}); |
| auto link = Connection::CreateLE(handle, role, local_address, peer_address, conn_params, hci_); |
| |
| if (!advertising()) { |
| bt_log(DEBUG, "hci-le", "connection received without advertising!"); |
| return; |
| } |
| |
| if (!connect_callback_) { |
| bt_log(DEBUG, "hci-le", "connection received when not connectable!"); |
| return; |
| } |
| |
| // Assign the currently advertised address as the local address of the |
| // connection. |
| auto callback = std::move(connect_callback_); |
| StopAdvertisingInternal(); |
| |
| // Pass on the ownership. |
| callback(std::move(link)); |
| } |
| |
| } // namespace bt::hci |