| // 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 <zircon/assert.h> |
| |
| #include "garnet/drivers/bluetooth/lib/common/byte_buffer.h" |
| #include "garnet/drivers/bluetooth/lib/common/log.h" |
| #include "garnet/drivers/bluetooth/lib/hci/transport.h" |
| #include "garnet/drivers/bluetooth/lib/hci/util.h" |
| |
| namespace btlib { |
| namespace hci { |
| |
| namespace { |
| |
| // 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_view() |
| ->mutable_payload<LESetAdvertisingEnableCommandParams>() |
| ->advertising_enable = enable; |
| ZX_ASSERT(packet); |
| return packet; |
| } |
| |
| std::unique_ptr<CommandPacket> BuildSetAdvertisingData( |
| const common::ByteBuffer& data) { |
| auto packet = CommandPacket::New(kLESetAdvertisingData, |
| sizeof(LESetAdvertisingDataCommandParams)); |
| packet->mutable_view()->mutable_payload_data().SetToZeros(); |
| |
| auto params = packet->mutable_view() |
| ->mutable_payload<LESetAdvertisingDataCommandParams>(); |
| params->adv_data_length = data.size(); |
| |
| common::MutableBufferView adv_view(params->adv_data, params->adv_data_length); |
| data.Copy(&adv_view); |
| |
| return packet; |
| } |
| |
| std::unique_ptr<CommandPacket> BuildSetScanResponse( |
| const common::ByteBuffer& scan_rsp) { |
| auto packet = CommandPacket::New(kLESetScanResponseData, |
| sizeof(LESetScanResponseDataCommandParams)); |
| packet->mutable_view()->mutable_payload_data().SetToZeros(); |
| |
| auto params = packet->mutable_view() |
| ->mutable_payload<LESetScanResponseDataCommandParams>(); |
| params->scan_rsp_data_length = scan_rsp.size(); |
| |
| common::MutableBufferView scan_data_view(params->scan_rsp_data, |
| sizeof(params->scan_rsp_data)); |
| scan_rsp.Copy(&scan_data_view); |
| |
| return packet; |
| } |
| |
| std::unique_ptr<CommandPacket> BuildSetRandomAddress( |
| const common::DeviceAddress& address) { |
| auto packet = CommandPacket::New(kLESetRandomAddress, |
| sizeof(LESetRandomAddressCommandParams)); |
| auto params = packet->mutable_view() |
| ->mutable_payload<LESetRandomAddressCommandParams>(); |
| params->random_address = address.value(); |
| return packet; |
| } |
| |
| std::unique_ptr<CommandPacket> BuildSetAdvertisingParams( |
| LEAdvertisingType type, |
| LEOwnAddressType own_address_type, |
| uint16_t interval_slices) { |
| auto packet = |
| CommandPacket::New(kLESetAdvertisingParameters, |
| sizeof(LESetAdvertisingParametersCommandParams)); |
| packet->mutable_view()->mutable_payload_data().SetToZeros(); |
| |
| // Cap the advertising interval based on the allowed range |
| // (Vol 2, Part E, 7.8.5) |
| if (interval_slices > kLEAdvertisingIntervalMax) { |
| interval_slices = kLEAdvertisingIntervalMax; |
| } else if (interval_slices < kLEAdvertisingIntervalMin) { |
| interval_slices = kLEAdvertisingIntervalMin; |
| } |
| |
| auto params = |
| packet->mutable_view() |
| ->mutable_payload<LESetAdvertisingParametersCommandParams>(); |
| params->adv_interval_min = htole16(interval_slices); |
| params->adv_interval_max = htole16(interval_slices); |
| 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; |
| } |
| |
| // This function is undefined outside the range that ms is valid: |
| // notabaly at 40960 ms it will produce undefined values. |
| uint16_t MillisecondsToTimeslices(uint16_t ms) { |
| return (uint16_t)(static_cast<uint32_t>(ms) * 1000 / 625); |
| } |
| |
| uint16_t TimeslicesToMilliseconds(uint16_t timeslices) { |
| // Promoted so we don't overflow |
| return (uint16_t)(static_cast<uint32_t>(timeslices) * 625 / 1000); |
| } |
| |
| } // namespace |
| |
| LegacyLowEnergyAdvertiser::LegacyLowEnergyAdvertiser(fxl::RefPtr<Transport> hci) |
| : hci_(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; |
| } |
| |
| void LegacyLowEnergyAdvertiser::StartAdvertising( |
| const common::DeviceAddress& address, |
| const common::ByteBuffer& data, |
| const common::ByteBuffer& scan_rsp, |
| ConnectionCallback connect_callback, |
| uint32_t interval_ms, |
| bool anonymous, |
| AdvertisingStatusCallback callback) { |
| ZX_DEBUG_ASSERT(callback); |
| ZX_DEBUG_ASSERT(address.type() != common::DeviceAddress::Type::kBREDR); |
| |
| if (anonymous) { |
| bt_log(TRACE, "hci-le", "anonymous advertising not supported"); |
| callback(0, Status(common::HostError::kNotSupported)); |
| return; |
| } |
| |
| if (advertising()) { |
| if (address != advertised_) { |
| bt_log(TRACE, "hci-le", "already advertising"); |
| callback(0, Status(common::HostError::kNotSupported)); |
| return; |
| } |
| bt_log(TRACE, "hci-le", "updating existing advertisement"); |
| } |
| |
| if (data.size() > GetSizeLimit()) { |
| bt_log(TRACE, "hci-le", "advertising data too large"); |
| callback(0, Status(common::HostError::kInvalidParameters)); |
| return; |
| } |
| |
| if (scan_rsp.size() > GetSizeLimit()) { |
| bt_log(TRACE, "hci-le", "scan response too large"); |
| callback(0, Status(common::HostError::kInvalidParameters)); |
| return; |
| } |
| |
| if (!hci_cmd_runner_->IsReady()) { |
| if (starting_) { |
| bt_log(TRACE, "hci-le", "already starting"); |
| callback(0, Status(common::HostError::kInProgress)); |
| return; |
| } |
| |
| // 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 (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)); |
| hci_cmd_runner_->QueueCommand(BuildSetScanResponse(scan_rsp)); |
| |
| // Set random address |
| if (!advertising() && |
| (address.type() != common::DeviceAddress::Type::kLEPublic)) { |
| hci_cmd_runner_->QueueCommand(BuildSetRandomAddress(address)); |
| } |
| |
| // Set advertising parameters |
| uint16_t interval_slices = MillisecondsToTimeslices(interval_ms); |
| LEAdvertisingType type = LEAdvertisingType::kAdvNonConnInd; |
| if (connect_callback) { |
| type = LEAdvertisingType::kAdvInd; |
| } else if (scan_rsp.size() > 0) { |
| type = LEAdvertisingType::kAdvScanInd; |
| } |
| |
| LEOwnAddressType own_addr_type; |
| if (address.type() == common::DeviceAddress::Type::kLEPublic) { |
| own_addr_type = LEOwnAddressType::kPublic; |
| } else { |
| own_addr_type = LEOwnAddressType::kRandom; |
| } |
| |
| hci_cmd_runner_->QueueCommand( |
| BuildSetAdvertisingParams(type, own_addr_type, interval_slices)); |
| |
| // Enable advertising. |
| hci_cmd_runner_->QueueCommand(BuildEnablePacket(GenericEnableParam::kEnable)); |
| |
| hci_cmd_runner_->RunCommands( |
| [this, address, interval_slices, callback = std::move(callback), |
| connect_callback = std::move(connect_callback)](Status status) mutable { |
| ZX_DEBUG_ASSERT(starting_); |
| starting_ = false; |
| |
| bt_log(TRACE, "hci-le", "advertising status: %s", |
| status.ToString().c_str()); |
| |
| uint16_t interval; |
| if (status) { |
| advertised_ = address; |
| connect_callback_ = std::move(connect_callback); |
| interval = TimeslicesToMilliseconds(interval_slices); |
| } else { |
| // Clear out the advertising data if it partially succeeded. |
| StopAdvertisingInternal(); |
| interval = 0; |
| } |
| |
| callback(interval, status); |
| }); |
| } |
| |
| bool LegacyLowEnergyAdvertiser::StopAdvertising( |
| const common::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(TRACE, "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(TRACE, "hci-le", "advertising stopped: ", status.ToString().c_str()); |
| }); |
| } |
| |
| void LegacyLowEnergyAdvertiser::OnIncomingConnection( |
| ConnectionHandle handle, Connection::Role role, |
| const common::DeviceAddress& peer_address, |
| const LEConnectionParameters& conn_params) { |
| if (!advertising()) { |
| bt_log(TRACE, "hci-le", "connection received without advertising!"); |
| return; |
| } |
| |
| if (!connect_callback_) { |
| bt_log(TRACE, "hci-le", "connection received when not connectable!"); |
| return; |
| } |
| |
| // Assign the currently advertised address as the local address of the |
| // connection. |
| auto local_address = advertised_; |
| auto callback = std::move(connect_callback_); |
| StopAdvertisingInternal(); |
| |
| // Assign the |advertised_| address as the connection's local address. |
| callback(Connection::CreateLE(handle, role, local_address, peer_address, |
| conn_params, hci_)); |
| } |
| |
| } // namespace hci |
| } // namespace btlib |