blob: 54bdef335fb002ec378f1d2a32a47aff9c6d518d [file] [log] [blame]
// Copyright 2019 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/pairing_state.h"
#include "src/connectivity/bluetooth/core/bt-host/common/log.h"
#include "src/connectivity/bluetooth/core/bt-host/hci/transport.h"
#include "src/connectivity/bluetooth/core/bt-host/sm/util.h"
namespace bt::gap {
using hci::AuthRequirements;
using hci::IOCapability;
using sm::util::IOCapabilityForHci;
PairingState::PairingState(PeerId peer_id, hci::Connection* link, PeerCache* peer_cache,
fit::closure auth_cb, StatusCallback status_cb)
: peer_id_(peer_id),
link_(link),
peer_cache_(peer_cache),
state_(State::kIdle),
send_auth_request_callback_(std::move(auth_cb)),
status_callback_(std::move(status_cb)) {
ZX_ASSERT(link_);
ZX_ASSERT(link_->ll_type() != hci::Connection::LinkType::kLE);
ZX_ASSERT(send_auth_request_callback_);
ZX_ASSERT(status_callback_);
link_->set_encryption_change_callback(fit::bind_member(this, &PairingState::OnEncryptionChange));
cleanup_cb_ = [](PairingState* self) {
self->link_->set_encryption_change_callback(nullptr);
auto callbacks_to_signal =
self->CompletePairingRequests(hci::Status(HostError::kLinkDisconnected));
bt_log(TRACE, "gap-bredr", "Signaling %zu unresolved pairing listeners for %#.4x",
callbacks_to_signal.size(), self->handle());
for (auto& cb : callbacks_to_signal) {
cb();
}
};
}
PairingState::~PairingState() {
if (cleanup_cb_) {
cleanup_cb_(this);
}
}
void PairingState::InitiatePairing(BrEdrSecurityRequirements security_requirements,
StatusCallback status_cb) {
// Raise an error to only the initiator—and not others—if we can't pair because there's no pairing
// delegate.
if (!pairing_delegate()) {
bt_log(DEBUG, "gap-bredr", "No pairing delegate for link %#.4x (id: %s); not pairing", handle(),
bt_str(peer_id()));
status_cb(handle(), hci::Status(HostError::kNotReady));
return;
}
if (state() == State::kIdle) {
ZX_ASSERT(!is_pairing());
// If the current link key already meets the security requirements, skip pairing and report
// success.
if (link_->ltk_type() &&
SecurityPropertiesMeetRequirements(sm::SecurityProperties(*link_->ltk_type()),
security_requirements)) {
status_cb(handle(), hci::Status());
return;
}
// TODO(fxbug.dev/55770): If current IO capabilities would make meeting security requirements
// impossible, skip pairing and report failure immediately.
current_pairing_ = Pairing::MakeInitiator(security_requirements);
PairingRequest request{.security_requirements = security_requirements,
.status_callback = std::move(status_cb)};
request_queue_.push_back(std::move(request));
bt_log(DEBUG, "gap-bredr", "Initiating pairing on %#.4x (id %s)", handle(), bt_str(peer_id()));
state_ = State::kInitiatorWaitLinkKeyRequest;
send_auth_request_callback_();
return;
}
// More than one consumer may wish to initiate pairing (e.g. concurrent outbound L2CAP channels),
// but each should wait for the results of any ongoing pairing procedure instead of sending their
// own Authentication Request.
if (is_pairing()) {
ZX_ASSERT(state() != State::kIdle);
bt_log(DEBUG, "gap-bredr", "Already pairing %#.4x (id: %s); blocking callback on completion",
handle(), bt_str(peer_id()));
PairingRequest request{.security_requirements = security_requirements,
.status_callback = std::move(status_cb)};
request_queue_.push_back(std::move(request));
} else {
// In the error state, we should expect no pairing to be created and cancel this particular
// request immediately.
ZX_ASSERT(state() == State::kFailed);
status_cb(handle(), hci::Status(HostError::kCanceled));
}
}
void PairingState::InitiateNextPairingRequest() {
ZX_ASSERT(state() == State::kIdle);
ZX_ASSERT(!is_pairing());
if (request_queue_.empty()) {
return;
}
PairingRequest& request = request_queue_.front();
current_pairing_ = Pairing::MakeInitiator(request.security_requirements);
bt_log(DEBUG, "gap-bredr", "Initiating queued pairing on %#.4x (id %s)", handle(),
bt_str(peer_id()));
state_ = State::kInitiatorWaitLinkKeyRequest;
send_auth_request_callback_();
}
std::optional<IOCapability> PairingState::OnIoCapabilityRequest() {
if (state() == State::kInitiatorWaitIoCapRequest) {
ZX_ASSERT(initiator());
ZX_ASSERT_MSG(pairing_delegate(), "PairingDelegate was reset after pairing began");
// TODO(fxbug.dev/37447): PairingDelegate may be reset if bt-gap exits and clears
// PairingDelegate (which is processed on a different thread).
current_pairing_->local_iocap =
sm::util::IOCapabilityForHci(pairing_delegate()->io_capability());
state_ = State::kInitiatorWaitIoCapResponse;
} else if (state() == State::kResponderWaitIoCapRequest) {
ZX_ASSERT(is_pairing());
ZX_ASSERT(!initiator());
// Raise an error if we can't respond to a pairing request because there's no pairing delegate.
if (!pairing_delegate()) {
bt_log(ERROR, "gap-bredr", "No pairing delegate for link %#.4x (id: %s); not pairing",
handle(), bt_str(peer_id()));
state_ = State::kIdle;
SignalStatus(hci::Status(HostError::kNotReady));
return std::nullopt;
}
// TODO(fxbug.dev/37447): PairingDelegate may be reset if bt-gap exits and clears
// PairingDelegate (which is processed on a different thread).
current_pairing_->local_iocap =
sm::util::IOCapabilityForHci(pairing_delegate()->io_capability());
current_pairing_->ComputePairingData();
state_ = GetStateForPairingEvent(current_pairing_->expected_event);
} else {
FailWithUnexpectedEvent(__func__);
return std::nullopt;
}
return current_pairing_->local_iocap;
}
void PairingState::OnIoCapabilityResponse(IOCapability peer_iocap) {
if (state() == State::kIdle) {
ZX_ASSERT(!is_pairing());
current_pairing_ = Pairing::MakeResponder(peer_iocap);
// Defer gathering local IO Capability until OnIoCapabilityRequest, where
// the pairing can be rejected if there's no pairing delegate.
state_ = State::kResponderWaitIoCapRequest;
} else if (state() == State::kInitiatorWaitIoCapResponse) {
ZX_ASSERT(initiator());
current_pairing_->peer_iocap = peer_iocap;
current_pairing_->ComputePairingData();
state_ = GetStateForPairingEvent(current_pairing_->expected_event);
} else {
FailWithUnexpectedEvent(__func__);
}
}
void PairingState::OnUserConfirmationRequest(uint32_t numeric_value, UserConfirmationCallback cb) {
if (state() != State::kWaitUserConfirmationRequest) {
FailWithUnexpectedEvent(__func__);
cb(false);
return;
}
ZX_ASSERT(is_pairing());
// TODO(fxbug.dev/37447): Reject pairing if pairing delegate went away.
ZX_ASSERT(pairing_delegate());
state_ = State::kWaitPairingComplete;
// PairingAction::kDisplayPasskey indicates that this device has a display and performs "Numeric
// Comparison with automatic confirmation" but auto-confirmation is delegated to PairingDelegate.
if (current_pairing_->action == PairingAction::kDisplayPasskey ||
current_pairing_->action == PairingAction::kComparePasskey) {
auto pairing = current_pairing_->GetWeakPtr();
auto confirm_cb = [this, cb = std::move(cb), pairing](bool confirm) mutable {
if (!pairing) {
return;
}
bt_log(DEBUG, "gap-bredr", "%#.4x (id: %s): %sing User Confirmation Request", handle(),
bt_str(peer_id()), confirm ? "Confirm" : "Cancel");
cb(confirm);
};
pairing_delegate()->DisplayPasskey(peer_id(), numeric_value,
PairingDelegate::DisplayMethod::kComparison,
std::move(confirm_cb));
} else if (current_pairing_->action == PairingAction::kGetConsent) {
auto pairing = current_pairing_->GetWeakPtr();
auto confirm_cb = [this, cb = std::move(cb), pairing](bool confirm) mutable {
if (!pairing) {
return;
}
bt_log(DEBUG, "gap-bredr", "%#.4x (id: %s): %sing User Confirmation Request", handle(),
bt_str(peer_id()), confirm ? "Confirm" : "Cancel");
cb(confirm);
};
pairing_delegate()->ConfirmPairing(peer_id(), std::move(confirm_cb));
} else {
ZX_ASSERT_MSG(current_pairing_->action == PairingAction::kAutomatic,
"%#.4x (id: %s): unexpected action %d", handle(), bt_str(peer_id()),
current_pairing_->action);
bt_log(DEBUG, "gap-bredr", "%#.4x (id: %s): automatically confirming User Confirmation Request",
handle(), bt_str(peer_id()));
cb(true);
}
}
void PairingState::OnUserPasskeyRequest(UserPasskeyCallback cb) {
if (state() != State::kWaitUserPasskeyRequest) {
FailWithUnexpectedEvent(__func__);
cb(std::nullopt);
return;
}
ZX_ASSERT(is_pairing());
// TODO(fxbug.dev/37447): Reject pairing if pairing delegate went away.
ZX_ASSERT(pairing_delegate());
state_ = State::kWaitPairingComplete;
ZX_ASSERT_MSG(current_pairing_->action == PairingAction::kRequestPasskey,
"%#.4x (id: %s): unexpected action %d", handle(), bt_str(peer_id()),
current_pairing_->action);
auto pairing = current_pairing_->GetWeakPtr();
auto passkey_cb = [this, cb = std::move(cb), pairing](int64_t passkey) mutable {
if (!pairing) {
return;
}
bt_log(DEBUG, "gap-bredr", "%#.4x (id: %s): Replying %" PRId64 " to User Passkey Request",
handle(), bt_str(peer_id()), passkey);
if (passkey >= 0) {
cb(static_cast<uint32_t>(passkey));
} else {
cb(std::nullopt);
}
};
pairing_delegate()->RequestPasskey(peer_id(), std::move(passkey_cb));
}
void PairingState::OnUserPasskeyNotification(uint32_t numeric_value) {
if (state() != State::kWaitUserPasskeyNotification) {
FailWithUnexpectedEvent(__func__);
return;
}
ZX_ASSERT(is_pairing());
// TODO(fxbug.dev/37447): Reject pairing if pairing delegate went away.
ZX_ASSERT(pairing_delegate());
state_ = State::kWaitPairingComplete;
auto pairing = current_pairing_->GetWeakPtr();
auto confirm_cb = [this, pairing](bool confirm) {
if (!pairing) {
return;
}
bt_log(DEBUG, "gap-bredr", "%#.4x (id: %s): Can't %s pairing from Passkey Notification side",
handle(), bt_str(peer_id()), confirm ? "confirm" : "cancel");
};
pairing_delegate()->DisplayPasskey(
peer_id(), numeric_value, PairingDelegate::DisplayMethod::kPeerEntry, std::move(confirm_cb));
}
void PairingState::OnSimplePairingComplete(hci::StatusCode status_code) {
if (state() != State::kWaitPairingComplete) {
FailWithUnexpectedEvent(__func__);
return;
}
ZX_ASSERT(is_pairing());
if (const hci::Status status(status_code);
bt_is_error(status, INFO, "gap-bredr", "Pairing failed on link %#.4x (id: %s)", handle(),
bt_str(peer_id()))) {
// TODO(fxbug.dev/37447): Checking pairing_delegate() for reset like this isn't thread safe.
if (pairing_delegate()) {
pairing_delegate()->CompletePairing(peer_id(), sm::Status(HostError::kFailed));
}
state_ = State::kFailed;
SignalStatus(status);
return;
}
pairing_delegate()->CompletePairing(peer_id(), sm::Status());
state_ = State::kWaitLinkKey;
}
std::optional<hci::LinkKey> PairingState::OnLinkKeyRequest(DeviceAddress address) {
if (state() != State::kIdle && state() != State::kInitiatorWaitLinkKeyRequest) {
FailWithUnexpectedEvent(__func__);
return std::nullopt;
}
auto* peer = peer_cache_->FindByAddress(address);
if (!peer) {
bt_log(ERROR, "gap-bredr", "no peer with address %s found", bt_str(address));
SignalStatus(hci::Status(HostError::kFailed));
return std::nullopt;
}
std::optional<sm::LTK> link_key;
if (peer->bredr() && peer->bredr()->bonded()) {
bt_log(INFO, "gap-bredr", "recalling link key for bonded peer %s", bt_str(peer->identifier()));
ZX_ASSERT(peer->bredr()->link_key().has_value());
link_key = peer->bredr()->link_key();
ZX_ASSERT(link_key->security().enc_key_size() == hci::kBrEdrLinkKeySize);
const auto link_key_type = link_key->security().GetLinkKeyType();
ZX_ASSERT(link_key_type.has_value());
link_->set_bredr_link_key(link_key->key(), link_key_type.value());
} else {
bt_log(INFO, "gap-bredr", "peer %s not bonded", bt_str(address));
}
// The link key request may be received outside of Simple Pairing (e.g. when the peer initiates
// the authentication procedure).
if (state() == State::kIdle) {
return link_key.has_value() ? link_key->key() : std::optional<hci::LinkKey>();
}
ZX_ASSERT(is_pairing());
if (link_key.has_value() && SecurityPropertiesMeetRequirements(
link_key->security(), current_pairing_->preferred_security)) {
// Skip Simple Pairing and just perform authentication with existing key.
state_ = State::kInitiatorWaitAuthComplete;
return link_key->key();
}
// Request that the controller perform Simple Pairing to generate a new key.
state_ = State::kInitiatorWaitIoCapRequest;
return std::nullopt;
}
void PairingState::OnLinkKeyNotification(const UInt128& link_key, hci::LinkKeyType key_type) {
// TODO(fxbug.dev/36360): We assume the controller is never in pairing debug mode because it's a
// security hazard to pair and bond using Debug Combination link keys.
ZX_ASSERT_MSG(key_type != hci::LinkKeyType::kDebugCombination,
"Pairing on link %#.4x (id: %s) resulted in insecure Debug Combination link key",
handle(), bt_str(peer_id()));
// When not pairing, only connection link key changes are allowed.
if (state() == State::kIdle && key_type == hci::LinkKeyType::kChangedCombination) {
if (!link_->ltk()) {
bt_log(WARN, "gap-bredr",
"Got Changed Combination key but link %#.4x (id: %s) has no current key", handle(),
bt_str(peer_id()));
state_ = State::kFailed;
SignalStatus(hci::Status(HostError::kInsufficientSecurity));
return;
}
bt_log(DEBUG, "gap-bredr", "Changing link key on %#.4x (id: %s)", handle(), bt_str(peer_id()));
link_->set_bredr_link_key(hci::LinkKey(link_key, 0, 0), key_type);
return;
} else if (state() != State::kWaitLinkKey) {
FailWithUnexpectedEvent(__func__);
return;
}
// The association model and resulting link security properties are computed by both the Link
// Manager (controller) and the host subsystem, so check that they agree.
ZX_ASSERT(is_pairing());
const sm::SecurityProperties sec_props = sm::SecurityProperties(key_type);
current_pairing_->security_properties = sec_props;
// Link keys resulting from legacy pairing are assigned lowest security level and we reject them.
if (sec_props.level() == sm::SecurityLevel::kNoSecurity) {
bt_log(WARN, "gap-bredr", "Link key (type %hhu) for %#.4x (id: %s) has insufficient security",
key_type, handle(), bt_str(peer_id()));
state_ = State::kFailed;
SignalStatus(hci::Status(HostError::kInsufficientSecurity));
return;
}
// If we performed an association procedure for MITM protection then expect the controller to
// produce a corresponding "authenticated" link key. Inversely, do not accept a link key reported
// as authenticated if we haven't performed the corresponding association procedure because it
// may provide a false high expectation of security to the user or application.
if (sec_props.authenticated() != current_pairing_->authenticated) {
bt_log(WARN, "gap-bredr", "Expected %sauthenticated link key for %#.4x (id: %s), got %hhu",
current_pairing_->authenticated ? "" : "un", handle(), bt_str(peer_id()), key_type);
state_ = State::kFailed;
SignalStatus(hci::Status(HostError::kInsufficientSecurity));
return;
}
link_->set_bredr_link_key(hci::LinkKey(link_key, 0, 0), key_type);
if (initiator()) {
state_ = State::kInitiatorWaitAuthComplete;
} else {
EnableEncryption();
}
}
void PairingState::OnAuthenticationComplete(hci::StatusCode status_code) {
if (state() != State::kInitiatorWaitAuthComplete) {
FailWithUnexpectedEvent(__func__);
return;
}
ZX_ASSERT(initiator());
if (const hci::Status status(status_code);
bt_is_error(status, INFO, "gap-bredr", "Authentication failed on link %#.4x (id: %s)",
handle(), bt_str(peer_id()))) {
state_ = State::kFailed;
SignalStatus(status);
return;
}
EnableEncryption();
}
void PairingState::OnEncryptionChange(hci::Status status, bool enabled) {
if (state() != State::kWaitEncryption) {
// Ignore encryption changes when not expecting them because they may be triggered by the peer
// at any time (v5.0 Vol 2, Part F, Sec 4.4).
bt_log(TRACE, "gap-bredr", "%#.4x (id: %s): %s(%s, %s) in state \"%s\"; taking no action",
handle(), bt_str(peer_id()), __func__, bt_str(status), enabled ? "true" : "false",
ToString(state()));
return;
}
if (status && !enabled) {
// With Secure Connections, encryption should never be disabled (v5.0 Vol 2,
// Part E, Sec 7.1.16) at all.
bt_log(WARN, "gap-bredr", "Pairing failed due to encryption disable on link %#.4x (id: %s)",
handle(), bt_str(peer_id()));
status = hci::Status(HostError::kFailed);
}
// Perform state transition.
if (status) {
// Reset state for another pairing.
state_ = State::kIdle;
} else {
state_ = State::kFailed;
}
SignalStatus(status);
}
std::unique_ptr<PairingState::Pairing> PairingState::Pairing::MakeInitiator(
BrEdrSecurityRequirements security_requirements) {
// Private ctor is inaccessible to std::make_unique.
std::unique_ptr<Pairing> pairing(new Pairing);
pairing->initiator = true;
pairing->preferred_security = security_requirements;
return pairing;
}
std::unique_ptr<PairingState::Pairing> PairingState::Pairing::MakeResponder(
hci::IOCapability peer_iocap) {
// Private ctor is inaccessible to std::make_unique.
std::unique_ptr<Pairing> pairing(new Pairing);
pairing->initiator = false;
pairing->peer_iocap = peer_iocap;
// Don't try to upgrade security as responder.
pairing->preferred_security = {.authentication = false, .secure_connections = false};
return pairing;
}
void PairingState::Pairing::ComputePairingData() {
if (initiator) {
action = GetInitiatorPairingAction(local_iocap, peer_iocap);
} else {
action = GetResponderPairingAction(peer_iocap, local_iocap);
}
expected_event = GetExpectedEvent(local_iocap, peer_iocap);
ZX_DEBUG_ASSERT(GetStateForPairingEvent(expected_event) != State::kFailed);
authenticated = IsPairingAuthenticated(local_iocap, peer_iocap);
bt_log(DEBUG, "gap-bredr",
"As %s with local %hhu/peer %hhu capabilities, expecting an %sauthenticated %u pairing "
"using %#x",
initiator ? "initiator" : "responder", local_iocap, peer_iocap, authenticated ? "" : "un",
action, expected_event);
}
const char* PairingState::ToString(PairingState::State state) {
switch (state) {
case State::kIdle:
return "Idle";
case State::kInitiatorWaitLinkKeyRequest:
return "InitiatorWaitLinkKeyRequest";
case State::kInitiatorWaitIoCapRequest:
return "InitiatorWaitIoCapRequest";
case State::kInitiatorWaitIoCapResponse:
return "InitiatorWaitIoCapResponse";
case State::kResponderWaitIoCapRequest:
return "ResponderWaitIoCapRequest";
case State::kWaitUserConfirmationRequest:
return "WaitUserConfirmationRequest";
case State::kWaitUserPasskeyRequest:
return "WaitUserPasskeyRequest";
case State::kWaitUserPasskeyNotification:
return "WaitUserPasskeyNotification";
case State::kWaitPairingComplete:
return "WaitPairingComplete";
case State::kWaitLinkKey:
return "WaitLinkKey";
case State::kInitiatorWaitAuthComplete:
return "InitiatorWaitAuthComplete";
case State::kWaitEncryption:
return "WaitEncryption";
case State::kFailed:
return "Failed";
default:
break;
}
return "";
}
PairingState::State PairingState::GetStateForPairingEvent(hci::EventCode event_code) {
switch (event_code) {
case hci::kUserConfirmationRequestEventCode:
return State::kWaitUserConfirmationRequest;
case hci::kUserPasskeyRequestEventCode:
return State::kWaitUserPasskeyRequest;
case hci::kUserPasskeyNotificationEventCode:
return State::kWaitUserPasskeyNotification;
default:
break;
}
return State::kFailed;
}
void PairingState::SignalStatus(hci::Status status) {
bt_log(TRACE, "gap-bredr", "Signaling pairing listeners for %#.4x (id: %s) with %s", handle(),
bt_str(peer_id()), bt_str(status));
// Collect the callbacks before invoking them so that CompletePairingRequests() can safely access
// members.
auto callbacks_to_signal = CompletePairingRequests(status);
// This PairingState may be destroyed by these callbacks (e.g. if signaling an error causes a
// disconnection), so care must be taken not to access any members.
status_callback_(handle(), status);
for (auto& cb : callbacks_to_signal) {
cb();
}
}
std::vector<fit::closure> PairingState::CompletePairingRequests(hci::Status status) {
std::vector<fit::closure> callbacks_to_signal;
if (!is_pairing()) {
ZX_ASSERT(request_queue_.empty());
return callbacks_to_signal;
}
if (!status.is_success()) {
// On pairing failure, signal all requests.
for (auto& request : request_queue_) {
callbacks_to_signal.push_back(
[handle = handle(), status, cb = std::move(request.status_callback)]() {
cb(handle, status);
});
}
request_queue_.clear();
current_pairing_ = nullptr;
return callbacks_to_signal;
}
ZX_ASSERT(state_ == State::kIdle);
ZX_ASSERT(link_->ltk_type().has_value());
auto security_properties = sm::SecurityProperties(link_->ltk_type().value());
// If a new link key was received, notify all callbacks because we always negotiate the best
// security possible. Even though pairing succeeded, send an error status if the individual
// request security requirements are not satisfied.
// TODO(fxbug.dev/1249): Only notify failure to callbacks of requests that have the same (or
// none) MITM requirements as the current pairing.
bool link_key_received = current_pairing_->security_properties.has_value();
if (link_key_received) {
for (auto& request : request_queue_) {
auto sec_props_satisfied =
SecurityPropertiesMeetRequirements(security_properties, request.security_requirements);
auto request_status =
sec_props_satisfied ? status : hci::Status(HostError::kInsufficientSecurity);
callbacks_to_signal.push_back(
[handle = handle(), request_status, cb = std::move(request.status_callback)]() {
cb(handle, request_status);
});
}
request_queue_.clear();
} else {
// If no new link key was received, then only authentication with an old key was performed
// (Simple Pairing was not required), and unsatisfied requests should initiate a new pairing
// rather than failing. If any pairing requests are satisfied by the existing key, notify them.
auto it = request_queue_.begin();
while (it != request_queue_.end()) {
if (!SecurityPropertiesMeetRequirements(security_properties, it->security_requirements)) {
it++;
continue;
}
callbacks_to_signal.push_back(
[handle = handle(), status, cb = std::move(it->status_callback)]() {
cb(handle, status);
});
it = request_queue_.erase(it);
}
}
current_pairing_ = nullptr;
InitiateNextPairingRequest();
return callbacks_to_signal;
}
void PairingState::EnableEncryption() {
if (!link_->StartEncryption()) {
bt_log(ERROR, "gap-bredr", "%#.4x (id: %s): Failed to enable encryption (state \"%s\")",
handle(), bt_str(peer_id()), ToString(state()));
status_callback_(link_->handle(), hci::Status(HostError::kFailed));
state_ = State::kFailed;
return;
}
state_ = State::kWaitEncryption;
}
void PairingState::FailWithUnexpectedEvent(const char* handler_name) {
bt_log(ERROR, "gap-bredr", "%#.4x (id: %s): Unexpected event %s while in state \"%s\"", handle(),
bt_str(peer_id()), handler_name, ToString(state()));
state_ = State::kFailed;
SignalStatus(hci::Status(HostError::kNotSupported));
}
PairingAction GetInitiatorPairingAction(IOCapability initiator_cap, IOCapability responder_cap) {
if (initiator_cap == IOCapability::kNoInputNoOutput) {
return PairingAction::kAutomatic;
}
if (responder_cap == IOCapability::kNoInputNoOutput) {
if (initiator_cap == IOCapability::kDisplayYesNo) {
return PairingAction::kGetConsent;
}
return PairingAction::kAutomatic;
}
if (initiator_cap == IOCapability::kKeyboardOnly) {
return PairingAction::kRequestPasskey;
}
if (responder_cap == IOCapability::kDisplayOnly) {
if (initiator_cap == IOCapability::kDisplayYesNo) {
return PairingAction::kComparePasskey;
}
return PairingAction::kAutomatic;
}
return PairingAction::kDisplayPasskey;
}
PairingAction GetResponderPairingAction(IOCapability initiator_cap, IOCapability responder_cap) {
if (initiator_cap == IOCapability::kNoInputNoOutput &&
responder_cap == IOCapability::kKeyboardOnly) {
return PairingAction::kGetConsent;
}
if (initiator_cap == IOCapability::kDisplayYesNo &&
responder_cap == IOCapability::kDisplayYesNo) {
return PairingAction::kComparePasskey;
}
return GetInitiatorPairingAction(responder_cap, initiator_cap);
}
hci::EventCode GetExpectedEvent(IOCapability local_cap, IOCapability peer_cap) {
if (local_cap == IOCapability::kNoInputNoOutput || peer_cap == IOCapability::kNoInputNoOutput) {
return hci::kUserConfirmationRequestEventCode;
}
if (local_cap == IOCapability::kKeyboardOnly) {
return hci::kUserPasskeyRequestEventCode;
}
if (peer_cap == IOCapability::kKeyboardOnly) {
return hci::kUserPasskeyNotificationEventCode;
}
return hci::kUserConfirmationRequestEventCode;
}
bool IsPairingAuthenticated(IOCapability local_cap, IOCapability peer_cap) {
if (local_cap == IOCapability::kNoInputNoOutput || peer_cap == IOCapability::kNoInputNoOutput) {
return false;
}
if (local_cap == IOCapability::kDisplayYesNo && peer_cap == IOCapability::kDisplayYesNo) {
return true;
}
if (local_cap == IOCapability::kKeyboardOnly || peer_cap == IOCapability::kKeyboardOnly) {
return true;
}
return false;
}
AuthRequirements GetInitiatorAuthRequirements(IOCapability local_cap) {
if (local_cap == IOCapability::kNoInputNoOutput) {
return AuthRequirements::kGeneralBonding;
}
return AuthRequirements::kMITMGeneralBonding;
}
AuthRequirements GetResponderAuthRequirements(IOCapability local_cap, IOCapability peer_cap) {
if (IsPairingAuthenticated(local_cap, peer_cap)) {
return AuthRequirements::kMITMGeneralBonding;
}
return AuthRequirements::kGeneralBonding;
}
} // namespace bt::gap