blob: e66d11f6e9f233762e3450394ff277e478cd1b88 [file] [log] [blame]
// 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/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(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::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(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 =
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(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(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 (!self) {
return;
}
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(TRACE, "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(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::kReadRemoteVersionInfoCompleteEventCode);
}
} // namespace bt::gap