| // Copyright 2020 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/gap/interrogator.h" |
| |
| #include <zircon/assert.h> |
| |
| #include "src/connectivity/bluetooth/core/bt-host/gap/peer.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci/hci.h" |
| #include "src/connectivity/bluetooth/core/bt-host/hci/transport.h" |
| |
| namespace bt::gap { |
| |
| Interrogator::Interrogation::Interrogation(PeerId peer_id, hci::ConnectionHandle handle, |
| ResultCallback result_cb) |
| : peer_id_(peer_id), |
| handle_(handle), |
| result_cb_(std::move(result_cb)), |
| weak_ptr_factory_(this) { |
| ZX_ASSERT(result_cb_); |
| } |
| |
| Interrogator::Interrogation::~Interrogation() { |
| // Interrogation may have already completed if there was an error. |
| // Otherwise, complete with success because all commands completed. |
| Complete(hci::Status()); |
| ZX_ASSERT_MSG(!result_cb_, "interrogation result callback never called"); |
| } |
| |
| void Interrogator::Interrogation::Complete(hci::Status status) { |
| // Each interrogation step may fail but only invoke the status callback once, for the earliest |
| // success/failure encountered. |
| if (!result_cb_) { |
| return; |
| } |
| |
| bt_log(SPEW, "gap", "interrogation completed (status: %s, peer id: %s)", bt_str(status), |
| bt_str(peer_id_)); |
| |
| result_cb_(status); |
| } |
| |
| Interrogator::Interrogator(PeerCache* cache, fxl::RefPtr<hci::Transport> hci, |
| async_dispatcher_t* dispatcher) |
| : hci_(hci), dispatcher_(dispatcher), cache_(cache), weak_ptr_factory_(this) { |
| ZX_ASSERT(hci_); |
| ZX_ASSERT(dispatcher_); |
| ZX_ASSERT(cache_); |
| } |
| |
| Interrogator::~Interrogator() { |
| for (auto& p : pending_) { |
| Cancel(p.first); |
| } |
| } |
| |
| void Interrogator::Start(PeerId peer_id, hci::ConnectionHandle handle, ResultCallback result_cb) { |
| ZX_ASSERT(result_cb); |
| |
| auto self = weak_ptr_factory_.GetWeakPtr(); |
| auto result_cb_wrapped = [self, peer_id, cb = std::move(result_cb)](hci::Status status) mutable { |
| if (!self) { |
| bt_log(TRACE, "gap", |
| "Interrogator already destroyed in interrogation result callback (peer id: %s)", |
| bt_str(peer_id)); |
| return; |
| } |
| auto node = self->pending_.extract(peer_id); |
| cb(status); |
| }; |
| |
| auto interrogation_ref = |
| fxl::MakeRefCounted<Interrogation>(peer_id, handle, std::move(result_cb_wrapped)); |
| |
| auto [it, inserted] = pending_.try_emplace(peer_id, interrogation_ref->GetWeakPtr()); |
| ZX_ASSERT_MSG(inserted, "interrogating peer %s twice at once", bt_str(peer_id)); |
| |
| bt_log(SPEW, "gap", "started interrogating peer %s", bt_str(peer_id)); |
| |
| SendCommands(std::move(interrogation_ref)); |
| } |
| |
| void Interrogator::Cancel(PeerId peer_id) { |
| async::PostTask(dispatcher_, [peer_id, self = weak_ptr_factory_.GetWeakPtr()]() { |
| if (!self) { |
| return; |
| } |
| |
| auto it = self->pending_.find(peer_id); |
| if (it == self->pending_.end()) { |
| return; |
| } |
| |
| // Result callback will remove Interrogation from |pending_|. |
| it->second->Complete(hci::Status(HostError::kCanceled)); |
| }); |
| } |
| |
| void Interrogator::ReadRemoteVersionInformation(InterrogationRefPtr interrogation) { |
| auto packet = hci::CommandPacket::New(hci::kReadRemoteVersionInfo, |
| sizeof(hci::ReadRemoteVersionInfoCommandParams)); |
| packet->mutable_payload<hci::ReadRemoteVersionInfoCommandParams>()->connection_handle = |
| htole16(interrogation->handle()); |
| |
| auto cmd_cb = [self = weak_ptr_factory_.GetWeakPtr(), interrogation]( |
| auto id, const hci::EventPacket& event) { |
| if (!interrogation->active()) { |
| return; |
| } |
| |
| if (hci_is_error(event, WARN, "gap", "read remote version info failed")) { |
| interrogation->Complete(event.ToStatus()); |
| return; |
| } |
| |
| if (event.event_code() == hci::kCommandStatusEventCode) { |
| return; |
| } |
| |
| ZX_ASSERT(event.event_code() == hci::kReadRemoteVersionInfoCompleteEventCode); |
| |
| bt_log(SPEW, "gap", "read remote version info completed (peer id: %s)", |
| bt_str(interrogation->peer_id())); |
| |
| const auto params = event.params<hci::ReadRemoteVersionInfoCompleteEventParams>(); |
| |
| Peer* peer = self->peer_cache()->FindById(interrogation->peer_id()); |
| if (!peer) { |
| interrogation->Complete(hci::Status(HostError::kFailed)); |
| return; |
| } |
| peer->set_version(params.lmp_version, params.manufacturer_name, params.lmp_subversion); |
| }; |
| |
| bt_log(SPEW, "gap", "asking for version info (peer id: %s)", bt_str(interrogation->peer_id())); |
| hci()->command_channel()->SendCommand(std::move(packet), dispatcher(), std::move(cmd_cb), |
| hci::kReadRemoteVersionInfoCompleteEventCode); |
| } |
| |
| } // namespace bt::gap |