| // 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-spec/protocol.h" |
| #include "src/connectivity/bluetooth/core/bt-host/transport/transport.h" |
| |
| namespace bt::gap { |
| |
| Interrogator::Interrogation::Interrogation(PeerId peer_id, hci_spec::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(fitx::ok()); |
| ZX_ASSERT_MSG(!result_cb_, "interrogation result callback never called"); |
| } |
| |
| void Interrogator::Interrogation::Complete(hci::Result<> 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(DEBUG, "gap", "interrogation completed (status: %s, peer: %s)", bt_str(status), |
| bt_str(peer_id_)); |
| |
| result_cb_(status); |
| } |
| |
| Interrogator::Interrogator(PeerCache* cache, fxl::WeakPtr<hci::Transport> hci) |
| : hci_(std::move(hci)), cache_(cache), weak_ptr_factory_(this) { |
| ZX_ASSERT(hci_); |
| ZX_ASSERT(cache_); |
| } |
| |
| Interrogator::~Interrogator() { |
| while (!pending_.empty()) { |
| auto peer_id = pending_.begin()->first; |
| // Result callback will erase this pending interrogation. |
| Cancel(peer_id); |
| } |
| } |
| |
| void Interrogator::Start(PeerId peer_id, hci_spec::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::Result<> status) mutable { |
| if (!self) { |
| bt_log(DEBUG, "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 = |
| fbl::AdoptRef(new 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(TRACE, "gap", "started interrogating peer %s", bt_str(peer_id)); |
| |
| SendCommands(std::move(interrogation_ref)); |
| } |
| |
| void Interrogator::Cancel(PeerId peer_id) { |
| auto it = pending_.find(peer_id); |
| if (it == pending_.end()) { |
| return; |
| } |
| |
| // Result callback will remove Interrogation from |pending_|. |
| it->second->Complete(ToResult(HostError::kCanceled)); |
| } |
| |
| void Interrogator::ReadRemoteVersionInformation(InterrogationRefPtr interrogation) { |
| auto packet = hci::CommandPacket::New(hci_spec::kReadRemoteVersionInfo, |
| sizeof(hci_spec::ReadRemoteVersionInfoCommandParams)); |
| packet->mutable_payload<hci_spec::ReadRemoteVersionInfoCommandParams>()->connection_handle = |
| htole16(interrogation->handle()); |
| |
| auto cmd_cb = [self = weak_ptr_factory_.GetWeakPtr(), interrogation]( |
| auto id, const hci::EventPacket& event) { |
| if (!self) { |
| return; |
| } |
| |
| if (!interrogation->active()) { |
| return; |
| } |
| |
| if (hci_is_error(event, WARN, "gap", "read remote version info failed")) { |
| interrogation->Complete(event.ToResult()); |
| return; |
| } |
| |
| if (event.event_code() == hci_spec::kCommandStatusEventCode) { |
| return; |
| } |
| |
| ZX_ASSERT(event.event_code() == hci_spec::kReadRemoteVersionInfoCompleteEventCode); |
| |
| bt_log(TRACE, "gap", "read remote version info completed (peer id: %s)", |
| bt_str(interrogation->peer_id())); |
| |
| const auto params = event.params<hci_spec::ReadRemoteVersionInfoCompleteEventParams>(); |
| |
| Peer* peer = self->peer_cache()->FindById(interrogation->peer_id()); |
| if (!peer) { |
| interrogation->Complete(ToResult(HostError::kFailed)); |
| return; |
| } |
| peer->set_version(params.lmp_version, params.manufacturer_name, params.lmp_subversion); |
| }; |
| |
| bt_log(TRACE, "gap", "asking for version info (peer id: %s)", bt_str(interrogation->peer_id())); |
| hci()->command_channel()->SendCommand(std::move(packet), std::move(cmd_cb), |
| hci_spec::kReadRemoteVersionInfoCompleteEventCode); |
| } |
| |
| } // namespace bt::gap |