| // Copyright 2018 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 "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/bredr_discovery_manager.h" |
| |
| #include <lib/fit/defer.h> |
| #include <lib/stdcompat/functional.h> |
| |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/assert.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/byte_buffer.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/log.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/common/supplement_data.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/gap/peer_cache.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci-spec/constants.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/transport/emboss_control_packets.h" |
| #include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/transport/transport.h" |
| |
| #include <pw_bluetooth/hci_commands.emb.h> |
| #include <pw_bluetooth/hci_events.emb.h> |
| |
| namespace bt::gap { |
| |
| namespace { |
| |
| // Make an existing peer connectable, or add a connectable peer if one does not |
| // already exist. |
| Peer* AddOrUpdateConnectablePeer(PeerCache* cache, const DeviceAddress& addr) { |
| Peer* peer = cache->FindByAddress(addr); |
| if (!peer) { |
| peer = cache->NewPeer(addr, /*connectable=*/true); |
| } else { |
| peer->set_connectable(true); |
| } |
| BT_ASSERT(peer); |
| return peer; |
| } |
| |
| std::unordered_set<Peer*> ProcessInquiryResultEvent( |
| PeerCache* cache, |
| const pw::bluetooth::emboss::InquiryResultWithRssiEventView& event) { |
| bt_log(TRACE, "gap-bredr", "inquiry result received"); |
| std::unordered_set<Peer*> updated; |
| auto responses = event.responses(); |
| for (auto response : responses) { |
| DeviceAddress addr(DeviceAddress::Type::kBREDR, |
| DeviceAddressBytes(response.bd_addr())); |
| Peer* peer = AddOrUpdateConnectablePeer(cache, addr); |
| peer->MutBrEdr().SetInquiryData(response); |
| updated.insert(peer); |
| } |
| return updated; |
| } |
| |
| } // namespace |
| |
| BrEdrDiscoverySession::BrEdrDiscoverySession( |
| BrEdrDiscoveryManager::WeakPtr manager) |
| : manager_(std::move(manager)) {} |
| |
| BrEdrDiscoverySession::~BrEdrDiscoverySession() { |
| manager_->RemoveDiscoverySession(this); |
| } |
| |
| void BrEdrDiscoverySession::NotifyDiscoveryResult(const Peer& peer) const { |
| if (peer_found_callback_) { |
| peer_found_callback_(peer); |
| } |
| } |
| |
| void BrEdrDiscoverySession::NotifyError() const { |
| if (error_callback_) { |
| error_callback_(); |
| } |
| } |
| |
| BrEdrDiscoverableSession::BrEdrDiscoverableSession( |
| BrEdrDiscoveryManager::WeakPtr manager) |
| : manager_(std::move(manager)) {} |
| |
| BrEdrDiscoverableSession::~BrEdrDiscoverableSession() { |
| manager_->RemoveDiscoverableSession(this); |
| } |
| |
| BrEdrDiscoveryManager::BrEdrDiscoveryManager( |
| pw::async::Dispatcher& pw_dispatcher, |
| hci::CommandChannel::WeakPtr cmd, |
| pw::bluetooth::emboss::InquiryMode mode, |
| PeerCache* peer_cache) |
| : cmd_(std::move(cmd)), |
| dispatcher_(pw_dispatcher), |
| cache_(peer_cache), |
| result_handler_id_(0u), |
| desired_inquiry_mode_(mode), |
| current_inquiry_mode_(pw::bluetooth::emboss::InquiryMode::STANDARD), |
| weak_self_(this) { |
| BT_DEBUG_ASSERT(cache_); |
| BT_DEBUG_ASSERT(cmd_.is_alive()); |
| |
| result_handler_id_ = cmd_->AddEventHandler( |
| hci_spec::kInquiryResultEventCode, |
| fit::bind_member<&BrEdrDiscoveryManager::InquiryResult>(this)); |
| BT_DEBUG_ASSERT(result_handler_id_); |
| rssi_handler_id_ = cmd_->AddEventHandler( |
| hci_spec::kInquiryResultWithRSSIEventCode, |
| cpp20::bind_front(&BrEdrDiscoveryManager::InquiryResultWithRssi, this)); |
| BT_DEBUG_ASSERT(rssi_handler_id_); |
| eir_handler_id_ = cmd_->AddEventHandler( |
| hci_spec::kExtendedInquiryResultEventCode, |
| cpp20::bind_front(&BrEdrDiscoveryManager::ExtendedInquiryResult, this)); |
| BT_DEBUG_ASSERT(eir_handler_id_); |
| |
| // Set the Inquiry Scan Settings |
| WriteInquiryScanSettings( |
| kInquiryScanInterval, kInquiryScanWindow, /*interlaced=*/true); |
| } |
| |
| BrEdrDiscoveryManager::~BrEdrDiscoveryManager() { |
| cmd_->RemoveEventHandler(eir_handler_id_); |
| cmd_->RemoveEventHandler(rssi_handler_id_); |
| cmd_->RemoveEventHandler(result_handler_id_); |
| InvalidateDiscoverySessions(); |
| } |
| |
| void BrEdrDiscoveryManager::RequestDiscovery(DiscoveryCallback callback) { |
| BT_DEBUG_ASSERT(callback); |
| |
| bt_log(INFO, "gap-bredr", "RequestDiscovery"); |
| |
| // If we're already waiting on a callback, then scanning is already starting. |
| // Queue this to create a session when the scanning starts. |
| if (!pending_discovery_.empty()) { |
| bt_log(DEBUG, "gap-bredr", "discovery starting, add to pending"); |
| pending_discovery_.push(std::move(callback)); |
| return; |
| } |
| |
| // If we're already scanning, just add a session. |
| if (!discovering_.empty() || !zombie_discovering_.empty()) { |
| bt_log(DEBUG, "gap-bredr", "add to active sessions"); |
| auto session = AddDiscoverySession(); |
| callback(fit::ok(), std::move(session)); |
| return; |
| } |
| |
| pending_discovery_.push(std::move(callback)); |
| MaybeStartInquiry(); |
| } |
| |
| // Starts the inquiry procedure if any sessions exist or are waiting to start. |
| void BrEdrDiscoveryManager::MaybeStartInquiry() { |
| if (pending_discovery_.empty() && discovering_.empty()) { |
| bt_log(DEBUG, "gap-bredr", "no sessions, not starting inquiry"); |
| return; |
| } |
| |
| bt_log(TRACE, "gap-bredr", "starting inquiry"); |
| |
| auto self = weak_self_.GetWeakPtr(); |
| if (desired_inquiry_mode_ != current_inquiry_mode_) { |
| auto packet = hci::EmbossCommandPacket::New< |
| pw::bluetooth::emboss::WriteInquiryModeCommandWriter>( |
| hci_spec::kWriteInquiryMode); |
| packet.view_t().inquiry_mode().Write(desired_inquiry_mode_); |
| cmd_->SendCommand( |
| std::move(packet), |
| [self, mode = desired_inquiry_mode_](auto /*unused*/, |
| const hci::EventPacket& event) { |
| if (!self.is_alive()) { |
| return; |
| } |
| |
| if (!hci_is_error( |
| event, ERROR, "gap-bredr", "write inquiry mode failed")) { |
| self->current_inquiry_mode_ = mode; |
| } |
| }); |
| } |
| |
| auto inquiry = hci::EmbossCommandPacket::New< |
| pw::bluetooth::emboss::InquiryCommandWriter>(hci_spec::kInquiry); |
| auto view = inquiry.view_t(); |
| view.lap().Write(pw::bluetooth::emboss::InquiryAccessCode::GIAC); |
| view.inquiry_length().Write(kInquiryLengthDefault); |
| view.num_responses().Write(0); |
| |
| cmd_->SendExclusiveCommand( |
| std::move(inquiry), |
| [self](auto, const hci::EventPacket& event) { |
| if (!self.is_alive()) { |
| return; |
| } |
| auto status = event.ToResult(); |
| if (bt_is_error(status, WARN, "gap-bredr", "inquiry error")) { |
| // Failure of some kind, signal error to the sessions. |
| self->InvalidateDiscoverySessions(); |
| |
| // Fallthrough for callback to pending sessions. |
| } |
| |
| // Resolve the request if the controller sent back a Command Complete or |
| // Status event. |
| // TODO(https://fxbug.dev/42062242): Make it impossible for Command |
| // Complete to happen here and remove handling for it. |
| if (event.event_code() == hci_spec::kCommandStatusEventCode || |
| event.event_code() == hci_spec::kCommandCompleteEventCode) { |
| // Inquiry started, make sessions for our waiting callbacks. |
| while (!self->pending_discovery_.empty()) { |
| auto callback = std::move(self->pending_discovery_.front()); |
| self->pending_discovery_.pop(); |
| callback(status, |
| (status.is_ok() ? self->AddDiscoverySession() : nullptr)); |
| } |
| return; |
| } |
| |
| BT_DEBUG_ASSERT(event.event_code() == |
| hci_spec::kInquiryCompleteEventCode); |
| self->zombie_discovering_.clear(); |
| |
| if (bt_is_error(status, TRACE, "gap", "inquiry complete error")) { |
| return; |
| } |
| |
| // We've stopped scanning because we timed out. |
| bt_log(TRACE, "gap-bredr", "inquiry complete, restart"); |
| self->MaybeStartInquiry(); |
| }, |
| hci_spec::kInquiryCompleteEventCode, |
| {hci_spec::kRemoteNameRequest}); |
| } |
| |
| // Stops the inquiry procedure. |
| void BrEdrDiscoveryManager::StopInquiry() { |
| BT_DEBUG_ASSERT(result_handler_id_); |
| bt_log(TRACE, "gap-bredr", "cancelling inquiry"); |
| |
| const hci::EmbossCommandPacket inq_cancel = hci::EmbossCommandPacket::New< |
| pw::bluetooth::emboss::InquiryCancelCommandView>( |
| hci_spec::kInquiryCancel); |
| cmd_->SendCommand( |
| std::move(inq_cancel), [](int64_t, const hci::EventPacket& event) { |
| // Warn if the command failed. |
| hci_is_error(event, WARN, "gap-bredr", "inquiry cancel failed"); |
| }); |
| } |
| |
| hci::CommandChannel::EventCallbackResult BrEdrDiscoveryManager::InquiryResult( |
| const hci::EmbossEventPacket& event) { |
| BT_DEBUG_ASSERT(event.event_code() == hci_spec::kInquiryResultEventCode); |
| std::unordered_set<Peer*> peers; |
| |
| auto view = event.view<pw::bluetooth::emboss::InquiryResultEventView>(); |
| for (int i = 0; i < view.num_responses().Read(); i++) { |
| const auto response = view.responses()[i]; |
| DeviceAddress addr(DeviceAddress::Type::kBREDR, |
| DeviceAddressBytes{response.bd_addr()}); |
| Peer* peer = AddOrUpdateConnectablePeer(cache_, addr); |
| peer->MutBrEdr().SetInquiryData(response); |
| peers.insert(peer); |
| } |
| |
| NotifyPeersUpdated(peers); |
| |
| return hci::CommandChannel::EventCallbackResult::kContinue; |
| } |
| |
| hci::CommandChannel::EventCallbackResult |
| BrEdrDiscoveryManager::InquiryResultWithRssi( |
| const hci::EmbossEventPacket& event) { |
| std::unordered_set<Peer*> peers = ProcessInquiryResultEvent( |
| cache_, |
| event.view<pw::bluetooth::emboss::InquiryResultWithRssiEventView>()); |
| NotifyPeersUpdated(peers); |
| return hci::CommandChannel::EventCallbackResult::kContinue; |
| } |
| |
| hci::CommandChannel::EventCallbackResult |
| BrEdrDiscoveryManager::ExtendedInquiryResult( |
| const hci::EmbossEventPacket& event) { |
| bt_log(TRACE, "gap-bredr", "ExtendedInquiryResult received"); |
| const auto result = |
| event.view<pw::bluetooth::emboss::ExtendedInquiryResultEventView>(); |
| |
| DeviceAddress addr(DeviceAddress::Type::kBREDR, |
| DeviceAddressBytes(result.bd_addr())); |
| Peer* peer = AddOrUpdateConnectablePeer(cache_, addr); |
| peer->MutBrEdr().SetInquiryData(result); |
| |
| NotifyPeersUpdated({peer}); |
| return hci::CommandChannel::EventCallbackResult::kContinue; |
| } |
| |
| void BrEdrDiscoveryManager::UpdateEIRResponseData( |
| std::string name, hci::ResultFunction<> callback) { |
| DataType name_type = DataType::kCompleteLocalName; |
| size_t name_size = name.size(); |
| if (name.size() >= (hci_spec::kExtendedInquiryResponseMaxNameBytes)) { |
| name_type = DataType::kShortenedLocalName; |
| name_size = hci_spec::kExtendedInquiryResponseMaxNameBytes; |
| } |
| auto self = weak_self_.GetWeakPtr(); |
| |
| auto write_eir = hci::EmbossCommandPacket::New< |
| pw::bluetooth::emboss::WriteExtendedInquiryResponseCommandWriter>( |
| hci_spec::kWriteExtendedInquiryResponse); |
| auto write_eir_params = write_eir.view_t(); |
| write_eir_params.fec_required().Write(0x00); |
| |
| // Create MutableBufferView of BackingStorage |
| unsigned char* eir_data = |
| write_eir_params.extended_inquiry_response().BackingStorage().data(); |
| MutableBufferView eir_response_buf = |
| MutableBufferView(eir_data, hci_spec::kExtendedInquiryResponseBytes); |
| eir_response_buf.Fill(0); |
| eir_response_buf[0] = name_size + 1; |
| eir_response_buf[1] = static_cast<uint8_t>(name_type); |
| eir_response_buf.mutable_view(2).Write( |
| reinterpret_cast<const uint8_t*>(name.data()), name_size); |
| |
| self->cmd_->SendCommand( |
| std::move(write_eir), |
| [self, name = std::move(name), cb = std::move(callback)]( |
| auto, const hci::EventPacket& event) mutable { |
| if (!hci_is_error(event, WARN, "gap", "write EIR failed")) { |
| self->local_name_ = std::move(name); |
| } |
| cb(event.ToResult()); |
| }); |
| } |
| |
| void BrEdrDiscoveryManager::UpdateLocalName(std::string name, |
| hci::ResultFunction<> callback) { |
| auto self = weak_self_.GetWeakPtr(); |
| |
| auto write_name = hci::EmbossCommandPacket::New< |
| pw::bluetooth::emboss::WriteLocalNameCommandWriter>( |
| hci_spec::kWriteLocalName); |
| auto write_name_view = write_name.view_t(); |
| auto local_name = write_name_view.local_name().BackingStorage(); |
| size_t name_size = std::min(name.size(), hci_spec::kMaxNameLength); |
| |
| // Use ContiguousBuffer instead of constructing LocalName view in case of |
| // invalid view being created when name is not large enough for the view |
| auto name_buf = emboss::support::ReadOnlyContiguousBuffer(&name); |
| local_name.CopyFrom(name_buf, name_size); |
| |
| cmd_->SendCommand( |
| std::move(write_name), |
| [self, name = std::move(name), cb = std::move(callback)]( |
| auto, const hci::EventPacket& event) mutable { |
| if (hci_is_error(event, WARN, "gap", "set local name failed")) { |
| cb(event.ToResult()); |
| return; |
| } |
| // If the WriteLocalName command was successful, update the extended |
| // inquiry data. |
| self->UpdateEIRResponseData(std::move(name), std::move(cb)); |
| }); |
| } |
| |
| void BrEdrDiscoveryManager::AttachInspect(inspect::Node& parent, |
| std::string name) { |
| auto node = parent.CreateChild(name); |
| inspect_properties_.Initialize(std::move(node)); |
| UpdateInspectProperties(); |
| } |
| |
| void BrEdrDiscoveryManager::InspectProperties::Initialize( |
| inspect::Node new_node) { |
| discoverable_sessions = new_node.CreateUint("discoverable_sessions", 0); |
| pending_discoverable_sessions = |
| new_node.CreateUint("pending_discoverable", 0); |
| discoverable_sessions_count = |
| new_node.CreateUint("discoverable_sessions_count", 0); |
| last_discoverable_length_sec = |
| new_node.CreateUint("last_discoverable_length_sec", 0); |
| |
| discovery_sessions = new_node.CreateUint("discovery_sessions", 0); |
| last_inquiry_length_sec = new_node.CreateUint("last_inquiry_length_sec", 0); |
| inquiry_sessions_count = new_node.CreateUint("inquiry_sessions_count", 0); |
| |
| discoverable_started_time.reset(); |
| inquiry_started_time.reset(); |
| |
| node = std::move(new_node); |
| } |
| |
| void BrEdrDiscoveryManager::InspectProperties::Update( |
| size_t discoverable_count, |
| size_t pending_discoverable_count, |
| size_t discovery_count, |
| pw::chrono::SystemClock::time_point now) { |
| if (!node) { |
| return; |
| } |
| |
| if (!discoverable_started_time.has_value() && discoverable_count != 0) { |
| discoverable_started_time.emplace(now); |
| } else if (discoverable_started_time.has_value() && discoverable_count == 0) { |
| discoverable_sessions_count.Add(1); |
| pw::chrono::SystemClock::duration length = |
| now - discoverable_started_time.value(); |
| last_discoverable_length_sec.Set( |
| std::chrono::duration_cast<std::chrono::seconds>(length).count()); |
| discoverable_started_time.reset(); |
| } |
| |
| if (!inquiry_started_time.has_value() && discovery_count != 0) { |
| inquiry_started_time.emplace(now); |
| } else if (inquiry_started_time.has_value() && discovery_count == 0) { |
| inquiry_sessions_count.Add(1); |
| pw::chrono::SystemClock::duration length = |
| now - inquiry_started_time.value(); |
| last_inquiry_length_sec.Set( |
| std::chrono::duration_cast<std::chrono::seconds>(length).count()); |
| inquiry_started_time.reset(); |
| } |
| |
| discoverable_sessions.Set(discoverable_count); |
| pending_discoverable_sessions.Set(pending_discoverable_count); |
| discovery_sessions.Set(discovery_count); |
| } |
| |
| void BrEdrDiscoveryManager::UpdateInspectProperties() { |
| inspect_properties_.Update(discoverable_.size(), |
| pending_discoverable_.size(), |
| discovering_.size(), |
| dispatcher_.now()); |
| } |
| |
| void BrEdrDiscoveryManager::NotifyPeersUpdated( |
| const std::unordered_set<Peer*>& peers) { |
| for (Peer* peer : peers) { |
| if (!peer->name()) { |
| RequestPeerName(peer->identifier()); |
| } |
| for (const auto& session : discovering_) { |
| session->NotifyDiscoveryResult(*peer); |
| } |
| } |
| } |
| |
| void BrEdrDiscoveryManager::RequestPeerName(PeerId id) { |
| if (requesting_names_.count(id)) { |
| bt_log(TRACE, "gap-bredr", "already requesting name for %s", bt_str(id)); |
| return; |
| } |
| Peer* peer = cache_->FindById(id); |
| if (!peer) { |
| bt_log( |
| WARN, "gap-bredr", "cannot request name, unknown peer: %s", bt_str(id)); |
| return; |
| } |
| auto packet = hci::EmbossCommandPacket::New< |
| pw::bluetooth::emboss::RemoteNameRequestCommandWriter>( |
| hci_spec::kRemoteNameRequest); |
| auto params = packet.view_t(); |
| BT_DEBUG_ASSERT(peer->bredr()); |
| BT_DEBUG_ASSERT(peer->bredr()->page_scan_repetition_mode()); |
| params.bd_addr().CopyFrom(peer->address().value().view()); |
| params.page_scan_repetition_mode().Write( |
| *(peer->bredr()->page_scan_repetition_mode())); |
| if (peer->bredr()->clock_offset()) { |
| params.clock_offset().valid().Write(true); |
| uint16_t offset = peer->bredr()->clock_offset().value(); |
| params.clock_offset().clock_offset().Write(offset); |
| } |
| |
| auto cb = [id, self = weak_self_.GetWeakPtr()]( |
| auto, const hci::EmbossEventPacket& event) { |
| if (!self.is_alive()) { |
| return; |
| } |
| if (hci_is_error(event, TRACE, "gap-bredr", "remote name request failed")) { |
| self->requesting_names_.erase(id); |
| return; |
| } |
| |
| if (event.event_code() == hci_spec::kCommandStatusEventCode) { |
| return; |
| } |
| |
| BT_DEBUG_ASSERT(event.event_code() == |
| hci_spec::kRemoteNameRequestCompleteEventCode); |
| |
| self->requesting_names_.erase(id); |
| Peer* const peer = self->cache_->FindById(id); |
| if (!peer) { |
| return; |
| } |
| |
| auto params = |
| event.view<pw::bluetooth::emboss::RemoteNameRequestCompleteEventView>(); |
| emboss::support::ReadOnlyContiguousBuffer name = |
| params.remote_name().BackingStorage(); |
| const unsigned char* name_end = std::find(name.begin(), name.end(), '\0'); |
| std::string name_string(reinterpret_cast<const char*>(name.begin()), |
| reinterpret_cast<const char*>(name_end)); |
| peer->RegisterName(std::move(name_string), |
| Peer::NameSource::kNameDiscoveryProcedure); |
| }; |
| |
| auto cmd_id = |
| cmd_->SendExclusiveCommand(std::move(packet), |
| std::move(cb), |
| hci_spec::kRemoteNameRequestCompleteEventCode, |
| {hci_spec::kInquiry}); |
| if (cmd_id) { |
| requesting_names_.insert(id); |
| } |
| } |
| |
| void BrEdrDiscoveryManager::RequestDiscoverable(DiscoverableCallback callback) { |
| BT_DEBUG_ASSERT(callback); |
| |
| auto self = weak_self_.GetWeakPtr(); |
| auto result_cb = [self, cb = callback.share()](const hci::Result<>& result) { |
| cb(result, (result.is_ok() ? self->AddDiscoverableSession() : nullptr)); |
| }; |
| |
| auto update_inspect = |
| fit::defer([self]() { self->UpdateInspectProperties(); }); |
| |
| if (!pending_discoverable_.empty()) { |
| pending_discoverable_.push(std::move(result_cb)); |
| bt_log(INFO, |
| "gap-bredr", |
| "discoverable mode starting: %lu pending", |
| pending_discoverable_.size()); |
| return; |
| } |
| |
| // If we're already discoverable, just add a session. |
| if (!discoverable_.empty()) { |
| result_cb(fit::ok()); |
| return; |
| } |
| |
| pending_discoverable_.push(std::move(result_cb)); |
| SetInquiryScan(); |
| } |
| |
| void BrEdrDiscoveryManager::SetInquiryScan() { |
| bool enable = !discoverable_.empty() || !pending_discoverable_.empty(); |
| bt_log(INFO, |
| "gap-bredr", |
| "%sabling inquiry scan: %lu sessions, %lu pending", |
| (enable ? "en" : "dis"), |
| discoverable_.size(), |
| pending_discoverable_.size()); |
| |
| auto self = weak_self_.GetWeakPtr(); |
| auto scan_enable_cb = [self](auto, const hci::EventPacket& event) { |
| if (!self.is_alive()) { |
| return; |
| } |
| |
| auto status = event.ToResult(); |
| auto resolve_pending = fit::defer([self, &status]() { |
| while (!self->pending_discoverable_.empty()) { |
| auto cb = std::move(self->pending_discoverable_.front()); |
| self->pending_discoverable_.pop(); |
| cb(status); |
| } |
| }); |
| |
| if (bt_is_error(status, WARN, "gap-bredr", "read scan enable failed")) { |
| return; |
| } |
| |
| bool enable = |
| !self->discoverable_.empty() || !self->pending_discoverable_.empty(); |
| auto params = event.return_params<hci_spec::ReadScanEnableReturnParams>(); |
| uint8_t scan_type = params->scan_enable; |
| bool enabled = |
| scan_type & static_cast<uint8_t>(hci_spec::ScanEnableBit::kInquiry); |
| |
| if (enable == enabled) { |
| bt_log(INFO, |
| "gap-bredr", |
| "inquiry scan already %s", |
| (enable ? "enabled" : "disabled")); |
| return; |
| } |
| |
| if (enable) { |
| scan_type |= static_cast<uint8_t>(hci_spec::ScanEnableBit::kInquiry); |
| } else { |
| scan_type &= ~static_cast<uint8_t>(hci_spec::ScanEnableBit::kInquiry); |
| } |
| |
| auto write_enable = hci::EmbossCommandPacket::New< |
| pw::bluetooth::emboss::WriteScanEnableCommandWriter>( |
| hci_spec::kWriteScanEnable); |
| auto write_enable_view = write_enable.view_t(); |
| write_enable_view.scan_enable().inquiry().Write( |
| scan_type & static_cast<uint8_t>(hci_spec::ScanEnableBit::kInquiry)); |
| write_enable_view.scan_enable().page().Write( |
| scan_type & static_cast<uint8_t>(hci_spec::ScanEnableBit::kPage)); |
| resolve_pending.cancel(); |
| self->cmd_->SendCommand( |
| std::move(write_enable), [self](auto, const hci::EventPacket& event) { |
| if (!self.is_alive()) { |
| return; |
| } |
| |
| // Warn if the command failed |
| hci_is_error(event, WARN, "gap-bredr", "write scan enable failed"); |
| |
| while (!self->pending_discoverable_.empty()) { |
| auto cb = std::move(self->pending_discoverable_.front()); |
| self->pending_discoverable_.pop(); |
| cb(event.ToResult()); |
| } |
| self->UpdateInspectProperties(); |
| }); |
| }; |
| |
| auto read_enable = hci::EmbossCommandPacket::New< |
| pw::bluetooth::emboss::ReadScanEnableCommandWriter>( |
| hci_spec::kReadScanEnable); |
| cmd_->SendCommand(std::move(read_enable), std::move(scan_enable_cb)); |
| } |
| |
| void BrEdrDiscoveryManager::WriteInquiryScanSettings(uint16_t interval, |
| uint16_t window, |
| bool interlaced) { |
| // TODO(jamuraa): add a callback for success or failure? |
| auto write_activity = hci::EmbossCommandPacket::New< |
| pw::bluetooth::emboss::WriteInquiryScanActivityCommandWriter>( |
| hci_spec::kWriteInquiryScanActivity); |
| auto activity_params = write_activity.view_t(); |
| activity_params.inquiry_scan_interval().Write(interval); |
| activity_params.inquiry_scan_window().Write(window); |
| |
| cmd_->SendCommand( |
| std::move(write_activity), [](auto id, const hci::EventPacket& event) { |
| if (hci_is_error(event, |
| WARN, |
| "gap-bredr", |
| "write inquiry scan activity failed")) { |
| return; |
| } |
| bt_log(TRACE, "gap-bredr", "inquiry scan activity updated"); |
| }); |
| |
| auto write_type = hci::EmbossCommandPacket::New< |
| pw::bluetooth::emboss::WriteInquiryScanTypeCommandWriter>( |
| hci_spec::kWriteInquiryScanType); |
| auto type_params = write_type.view_t(); |
| type_params.inquiry_scan_type().Write( |
| interlaced ? pw::bluetooth::emboss::InquiryScanType::INTERLACED |
| : pw::bluetooth::emboss::InquiryScanType::STANDARD); |
| |
| cmd_->SendCommand( |
| std::move(write_type), [](auto id, const hci::EventPacket& event) { |
| if (hci_is_error( |
| event, WARN, "gap-bredr", "write inquiry scan type failed")) { |
| return; |
| } |
| bt_log(TRACE, "gap-bredr", "inquiry scan type updated"); |
| }); |
| } |
| |
| std::unique_ptr<BrEdrDiscoverySession> |
| BrEdrDiscoveryManager::AddDiscoverySession() { |
| bt_log(TRACE, "gap-bredr", "adding discovery session"); |
| |
| // Cannot use make_unique here since BrEdrDiscoverySession has a private |
| // constructor. |
| std::unique_ptr<BrEdrDiscoverySession> session( |
| new BrEdrDiscoverySession(weak_self_.GetWeakPtr())); |
| BT_DEBUG_ASSERT(discovering_.find(session.get()) == discovering_.end()); |
| discovering_.insert(session.get()); |
| bt_log(INFO, |
| "gap-bredr", |
| "new discovery session: %lu sessions active", |
| discovering_.size()); |
| UpdateInspectProperties(); |
| return session; |
| } |
| |
| void BrEdrDiscoveryManager::RemoveDiscoverySession( |
| BrEdrDiscoverySession* session) { |
| bt_log(TRACE, "gap-bredr", "removing discovery session"); |
| |
| auto removed = discovering_.erase(session); |
| // TODO(https://fxbug.dev/42145646): Cancel the running inquiry with |
| // StopInquiry() instead. |
| if (removed) { |
| zombie_discovering_.insert(session); |
| } |
| UpdateInspectProperties(); |
| } |
| |
| std::unique_ptr<BrEdrDiscoverableSession> |
| BrEdrDiscoveryManager::AddDiscoverableSession() { |
| bt_log(TRACE, "gap-bredr", "adding discoverable session"); |
| |
| // Cannot use make_unique here since BrEdrDiscoverableSession has a private |
| // constructor. |
| std::unique_ptr<BrEdrDiscoverableSession> session( |
| new BrEdrDiscoverableSession(weak_self_.GetWeakPtr())); |
| BT_DEBUG_ASSERT(discoverable_.find(session.get()) == discoverable_.end()); |
| discoverable_.insert(session.get()); |
| bt_log(INFO, |
| "gap-bredr", |
| "new discoverable session: %lu sessions active", |
| discoverable_.size()); |
| return session; |
| } |
| |
| void BrEdrDiscoveryManager::RemoveDiscoverableSession( |
| BrEdrDiscoverableSession* session) { |
| bt_log(DEBUG, "gap-bredr", "removing discoverable session"); |
| discoverable_.erase(session); |
| if (discoverable_.empty()) { |
| SetInquiryScan(); |
| } |
| UpdateInspectProperties(); |
| } |
| |
| void BrEdrDiscoveryManager::InvalidateDiscoverySessions() { |
| for (auto session : discovering_) { |
| session->NotifyError(); |
| } |
| discovering_.clear(); |
| UpdateInspectProperties(); |
| } |
| |
| } // namespace bt::gap |