blob: 3ac50946eacf1f18633daafca77a1d222a211777 [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-spec/constants.h"
#include "src/connectivity/bluetooth/core/bt-host/sm/util.h"
#include "src/connectivity/bluetooth/core/bt-host/transport/transport.h"
namespace bt::gap {
using hci_spec::AuthRequirements;
using hci_spec::IOCapability;
using sm::util::IOCapabilityForHci;
PairingState::PairingState(fxl::WeakPtr<Peer> peer, hci::BrEdrConnection* link, bool link_initiated,
fit::closure auth_cb, StatusCallback status_cb)
: peer_id_(peer->identifier()),
peer_(std::move(peer)),
link_(link),
outgoing_connection_(link_initiated),
peer_missing_key_(false),
state_(State::kIdle),
send_auth_request_callback_(std::move(auth_cb)),
status_callback_(std::move(status_cb)) {
ZX_ASSERT(link_);
ZX_ASSERT(send_auth_request_callback_);
ZX_ASSERT(status_callback_);
link_->set_encryption_change_callback(fit::bind_member<&PairingState::OnEncryptionChange>(this));
cleanup_cb_ = [](PairingState* self) {
self->link_->set_encryption_change_callback(nullptr);
auto callbacks_to_signal =
self->CompletePairingRequests(ToResult(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) {
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(), fitx::ok());
return;
}
// TODO(fxbug.dev/42403): If there is no pairing delegate set AND the current peer does not have
// a bonded link key, there is no way to upgrade the link security, so we don't need to bother
// calling `send_auth_request`.
//
// 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, outgoing_connection_);
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(INFO, "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(), ToResult(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, outgoing_connection_);
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 &&
state() != State::kResponderWaitIoCapRequest) {
FailWithUnexpectedEvent(__func__);
return std::nullopt;
}
// Log an error and return std::nullopt if we can't respond to a pairing request because there's
// no pairing delegate. This corresponds to the non-bondable state as outlined in spec v5.2 Vol.
// 3 Part C 4.3.1.
if (!pairing_delegate()) {
bt_log(WARN, "gap-bredr", "No pairing delegate set; not pairing link %#.4x (peer: %s)",
handle(), bt_str(peer_id()));
// We set the state_ to Idle instead of Failed because it is possible that a PairingDelegate
// will be set before the next pairing attempt, allowing it to succeed.
state_ = State::kIdle;
SignalStatus(ToResult(HostError::kNotReady));
return std::nullopt;
}
current_pairing_->local_iocap = sm::util::IOCapabilityForHci(pairing_delegate()->io_capability());
if (state() == State::kInitiatorWaitIoCapRequest) {
ZX_ASSERT(initiator());
state_ = State::kInitiatorWaitIoCapResponse;
} else {
ZX_ASSERT(is_pairing());
ZX_ASSERT(!initiator());
current_pairing_->ComputePairingData();
state_ = GetStateForPairingEvent(current_pairing_->expected_event);
}
return current_pairing_->local_iocap;
}
void PairingState::OnIoCapabilityResponse(IOCapability peer_iocap) {
// If we preivously provided a key for peer to pair, but that didn't work, they may try to
// re-pair. Cancel the previous pairing if they try to restart.
if (state() == State::kWaitEncryption) {
ZX_ASSERT(is_pairing());
current_pairing_ = nullptr;
state_ = State::kIdle;
}
if (state() == State::kIdle) {
ZX_ASSERT(!is_pairing());
current_pairing_ = Pairing::MakeResponder(peer_iocap, outgoing_connection_);
// 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;
bt_log_scope("%#.4x, id: %s", handle(), bt_str(peer_id()));
if (current_pairing_->action == PairingAction::kAutomatic) {
if (!outgoing_connection_) {
bt_log(ERROR, "gap-bredr", "automatically rejecting incoming link pairing");
} else {
bt_log(DEBUG, "gap-bredr", "automatically confirming outgoing link pairing");
}
cb(outgoing_connection_);
return;
}
auto confirm_cb = [cb = std::move(cb), log_ctx = capture_log_context(),
pairing = current_pairing_->GetWeakPtr()](bool confirm) mutable {
if (!pairing) {
return;
}
add_parent_context(log_ctx);
bt_log(DEBUG, "gap-bredr", "%sing User Confirmation Request", confirm ? "Confirm" : "Cancel");
cb(confirm);
};
// 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) {
pairing_delegate()->DisplayPasskey(peer_id(), numeric_value,
PairingDelegate::DisplayMethod::kComparison,
std::move(confirm_cb));
} else if (current_pairing_->action == PairingAction::kGetConsent) {
pairing_delegate()->ConfirmPairing(peer_id(), std::move(confirm_cb));
} else {
ZX_PANIC("%#.4x (id: %s): unexpected action %d", handle(), bt_str(peer_id()),
current_pairing_->action);
}
}
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_spec::StatusCode status_code) {
// The pairing process may fail early, which the controller will deliver as an Simple Pairing
// Complete with a non-success status. Log and proxy the error code.
if (const fitx::result result = ToResult(status_code);
is_pairing() &&
bt_is_error(result, 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(), ToResult(HostError::kFailed));
}
state_ = State::kFailed;
SignalStatus(result);
return;
}
// Handle successful Authentication Complete events that are not expected.
if (state() != State::kWaitPairingComplete) {
FailWithUnexpectedEvent(__func__);
return;
}
ZX_ASSERT(is_pairing());
pairing_delegate()->CompletePairing(peer_id(), fitx::ok());
state_ = State::kWaitLinkKey;
}
std::optional<hci_spec::LinkKey> PairingState::OnLinkKeyRequest() {
if (state() != State::kIdle && state() != State::kInitiatorWaitLinkKeyRequest) {
FailWithUnexpectedEvent(__func__);
return std::nullopt;
}
ZX_ASSERT(peer_);
std::optional<sm::LTK> link_key;
if (peer_missing_key_) {
bt_log(INFO, "gap-bredr", "peer %s missing key, ignoring our key", bt_str(peer_->identifier()));
} else 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_spec::kBrEdrLinkKeySize);
const auto link_key_type = link_key->security().GetLinkKeyType();
ZX_ASSERT(link_key_type.has_value());
link_->set_link_key(link_key->key(), link_key_type.value());
} else {
bt_log(INFO, "gap-bredr", "peer %s not bonded", bt_str(peer_->identifier()));
}
// The link key request may be received outside of Simple Pairing (e.g. when the peer initiates
// the authentication procedure).
if (state() == State::kIdle) {
if (link_key.has_value()) {
ZX_ASSERT(!is_pairing());
current_pairing_ = Pairing::MakeResponderForBonded();
state_ = State::kWaitEncryption;
return link_key->key();
}
return std::optional<hci_spec::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_spec::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_spec::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_spec::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(ToResult(HostError::kInsufficientSecurity));
return;
}
bt_log(DEBUG, "gap-bredr", "Changing link key on %#.4x (id: %s)", handle(), bt_str(peer_id()));
link_->set_link_key(hci_spec::LinkKey(link_key, 0, 0), key_type);
return;
}
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(ToResult(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(ToResult(HostError::kInsufficientSecurity));
return;
}
link_->set_link_key(hci_spec::LinkKey(link_key, 0, 0), key_type);
if (initiator()) {
state_ = State::kInitiatorWaitAuthComplete;
} else {
EnableEncryption();
}
}
void PairingState::OnAuthenticationComplete(hci_spec::StatusCode status_code) {
if (is_pairing() && peer_->bredr() && peer_->bredr()->bonded() &&
status_code == hci_spec::StatusCode::kPinOrKeyMissing) {
// We have provided our link key, but the remote side says they don't have a key.
// Pretend we don't have a link key, then start the pairing over.
// We will get consent even if we are otherwise kAutomatic
bt_log(INFO, "gap-bredr",
"Re-initiating pairing on %#.4x (id %s) as remote side reports no key.", handle(),
bt_str(peer_id()));
peer_missing_key_ = true;
current_pairing_->allow_automatic = false;
state_ = State::kInitiatorWaitLinkKeyRequest;
send_auth_request_callback_();
return;
}
// The pairing process may fail early, which the controller will deliver as an Authentication
// Complete with a non-success status. Log and proxy the error code.
if (const fitx::result result = ToResult(status_code);
bt_is_error(result, INFO, "gap-bredr", "Authentication failed on link %#.4x (id: %s)",
handle(), bt_str(peer_id()))) {
state_ = State::kFailed;
SignalStatus(result);
return;
}
// Handle successful Authentication Complete events that are not expected.
if (state() != State::kInitiatorWaitAuthComplete) {
FailWithUnexpectedEvent(__func__);
return;
}
ZX_ASSERT(initiator());
EnableEncryption();
}
void PairingState::OnEncryptionChange(hci::Result<bool> result) {
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(result),
result.is_ok() ? (result.value() ? "true" : "false") : "?", ToString(state()));
return;
}
if (result.is_ok() && !result.value()) {
// 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()));
result = fitx::error(Error(HostError::kFailed));
}
// Perform state transition.
if (result.is_ok()) {
// Reset state for another pairing.
state_ = State::kIdle;
} else {
state_ = State::kFailed;
}
SignalStatus(result.is_ok() ? hci::Result<>(fitx::ok()) : result.take_error());
}
std::unique_ptr<PairingState::Pairing> PairingState::Pairing::MakeInitiator(
BrEdrSecurityRequirements security_requirements, bool link_initiated) {
// Private ctor is inaccessible to std::make_unique.
std::unique_ptr<Pairing> pairing(new Pairing(link_initiated));
pairing->initiator = true;
pairing->preferred_security = security_requirements;
return pairing;
}
std::unique_ptr<PairingState::Pairing> PairingState::Pairing::MakeResponder(
hci_spec::IOCapability peer_iocap, bool link_initiated) {
// Private ctor is inaccessible to std::make_unique.
std::unique_ptr<Pairing> pairing(new Pairing(link_initiated));
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;
}
std::unique_ptr<PairingState::Pairing> PairingState::Pairing::MakeResponderForBonded() {
std::unique_ptr<Pairing> pairing(new Pairing(/* link initiated */ false));
pairing->initiator = false;
// 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);
}
if (!allow_automatic && action == PairingAction::kAutomatic) {
action = PairingAction::kGetConsent;
}
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%s",
initiator ? "initiator" : "responder", local_iocap, peer_iocap, authenticated ? "" : "un",
action, expected_event, allow_automatic ? "" : " (auto not allowed)");
}
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_spec::EventCode event_code) {
switch (event_code) {
case hci_spec::kUserConfirmationRequestEventCode:
return State::kWaitUserConfirmationRequest;
case hci_spec::kUserPasskeyRequestEventCode:
return State::kWaitUserPasskeyRequest;
case hci_spec::kUserPasskeyNotificationEventCode:
return State::kWaitUserPasskeyNotification;
default:
break;
}
return State::kFailed;
}
void PairingState::SignalStatus(hci::Result<> status) {
bt_log(INFO, "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::Result<> status) {
std::vector<fit::closure> callbacks_to_signal;
if (!is_pairing()) {
ZX_ASSERT(request_queue_.empty());
return callbacks_to_signal;
}
if (status.is_error()) {
// 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 : ToResult(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(), ToResult(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(ToResult(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_spec::EventCode GetExpectedEvent(IOCapability local_cap, IOCapability peer_cap) {
if (local_cap == IOCapability::kNoInputNoOutput || peer_cap == IOCapability::kNoInputNoOutput) {
return hci_spec::kUserConfirmationRequestEventCode;
}
if (local_cap == IOCapability::kKeyboardOnly) {
return hci_spec::kUserPasskeyRequestEventCode;
}
if (peer_cap == IOCapability::kKeyboardOnly) {
return hci_spec::kUserPasskeyNotificationEventCode;
}
return hci_spec::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