chromium/chromeos/ash/services/bluetooth_config/device_pairing_handler.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/services/bluetooth_config/device_pairing_handler.h"

#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/default_clock.h"
#include "components/device_event_log/device_event_log.h"
#include "device/bluetooth/bluetooth_common.h"
#include "device/bluetooth/chromeos/bluetooth_utils.h"

namespace ash::bluetooth_config {

namespace {

// The number of digits a passkey used for pairing will be. This is documented
// in device::BluetoothDevice.
const size_t kNumPasskeyDigits = 6;

// Converts |passkey| to a kNumPasskeyDigits-digit string, padding with zeroes
// if necessary.
std::string PasskeyToString(uint32_t passkey) {
  std::string passkey_string = base::NumberToString(passkey);

  // |passkey_string| should never be more than kNumPasskeyDigits digits long.
  DCHECK(passkey_string.length() <= kNumPasskeyDigits);
  return base::StrCat(
      {std::string(kNumPasskeyDigits - passkey_string.length(), '0'),
       passkey_string});
}

mojom::PairingResult GetPairingResult(
    std::optional<device::ConnectionFailureReason> failure_reason) {
  if (!failure_reason) {
    return mojom::PairingResult::kSuccess;
  }

  switch (failure_reason.value()) {
    case device::ConnectionFailureReason::kAuthTimeout:
      [[fallthrough]];
    case device::ConnectionFailureReason::kAuthFailed:
      [[fallthrough]];
    case device::ConnectionFailureReason::kAuthCanceled:
      [[fallthrough]];
    case device::ConnectionFailureReason::kAuthRejected:
      return mojom::PairingResult::kAuthFailed;

    case device::ConnectionFailureReason::kUnknownError:
      [[fallthrough]];
    case device::ConnectionFailureReason::kSystemError:
      [[fallthrough]];
    case device::ConnectionFailureReason::kFailed:
      [[fallthrough]];
    case device::ConnectionFailureReason::kUnknownConnectionError:
      [[fallthrough]];
    case device::ConnectionFailureReason::kUnsupportedDevice:
      [[fallthrough]];
    case device::ConnectionFailureReason::kNotConnectable:
      [[fallthrough]];
    case device::ConnectionFailureReason::kInprogress:
      [[fallthrough]];
    case device::ConnectionFailureReason::kNotFound:
      [[fallthrough]];
    case device::ConnectionFailureReason::kBluetoothDisabled:
      [[fallthrough]];
    case device::ConnectionFailureReason::kDeviceNotReady:
      [[fallthrough]];
    case device::ConnectionFailureReason::kAlreadyConnected:
      [[fallthrough]];
    case device::ConnectionFailureReason::kDeviceAlreadyExists:
      [[fallthrough]];
    case device::ConnectionFailureReason::kInvalidArgs:
      [[fallthrough]];
    case device::ConnectionFailureReason::kNonAuthTimeout:
      [[fallthrough]];
    case device::ConnectionFailureReason::kNoMemory:
      [[fallthrough]];
    case device::ConnectionFailureReason::kJniEnvironment:
      [[fallthrough]];
    case device::ConnectionFailureReason::kJniThreadAttach:
      [[fallthrough]];
    case device::ConnectionFailureReason::kWakelock:
      [[fallthrough]];
    case device::ConnectionFailureReason::kUnexpectedState:
      [[fallthrough]];
    case device::ConnectionFailureReason::kSocketError:
      return mojom::PairingResult::kNonAuthFailure;
  }
}

}  // namespace

DevicePairingHandler::DevicePairingHandler(
    mojo::PendingReceiver<mojom::DevicePairingHandler> pending_receiver,
    AdapterStateController* adapter_state_controller)
    : adapter_state_controller_(adapter_state_controller) {
  adapter_state_controller_observation_.Observe(
      adapter_state_controller_.get());
  receiver_.Bind(std::move(pending_receiver));
}

DevicePairingHandler::~DevicePairingHandler() = default;

void DevicePairingHandler::SendRequestPinCode() {
  BLUETOOTH_LOG(EVENT) << "Requesting pin code for "
                       << current_pairing_device_id_;
  delegate_->RequestPinCode(base::BindOnce(
      &DevicePairingHandler::OnRequestPinCode, weak_ptr_factory_.GetWeakPtr()));
}
void DevicePairingHandler::SendRequestPasskey() {
  BLUETOOTH_LOG(EVENT) << "Requesting passkey for "
                       << current_pairing_device_id_;
  delegate_->RequestPasskey(base::BindOnce(
      &DevicePairingHandler::OnRequestPasskey, weak_ptr_factory_.GetWeakPtr()));
}

void DevicePairingHandler::SendDisplayPinCode(const std::string& pin_code) {
  BLUETOOTH_LOG(EVENT) << "Displaying pin code for "
                       << current_pairing_device_id_
                       << ", pin code: " << pin_code;
  key_entered_handler_.reset();
  delegate_->DisplayPinCode(pin_code,
                            key_entered_handler_.BindNewPipeAndPassReceiver());
}

void DevicePairingHandler::SendDisplayPasskey(uint32_t passkey) {
  BLUETOOTH_LOG(EVENT) << "Displaying passkey for "
                       << current_pairing_device_id_
                       << ", passkey: " << passkey;
  key_entered_handler_.reset();
  delegate_->DisplayPasskey(PasskeyToString(passkey),
                            key_entered_handler_.BindNewPipeAndPassReceiver());
}

void DevicePairingHandler::SendKeysEntered(uint32_t entered) {
  BLUETOOTH_LOG(EVENT) << entered << " keys entered for "
                       << current_pairing_device_id_;
  key_entered_handler_->HandleKeyEntered(entered);
}

void DevicePairingHandler::SendConfirmPasskey(uint32_t passkey) {
  BLUETOOTH_LOG(EVENT) << "Confirming passkey for "
                       << current_pairing_device_id_
                       << ", passkey: " << passkey;
  delegate_->ConfirmPasskey(
      PasskeyToString(passkey),
      base::BindOnce(&DevicePairingHandler::OnConfirmPairing,
                     weak_ptr_factory_.GetWeakPtr()));
}

void DevicePairingHandler::SendAuthorizePairing() {
  BLUETOOTH_LOG(EVENT) << "Authorizing pairing for "
                       << current_pairing_device_id();
  delegate_->AuthorizePairing(base::BindOnce(
      &DevicePairingHandler::OnConfirmPairing, weak_ptr_factory_.GetWeakPtr()));
}

void DevicePairingHandler::FinishCurrentPairingRequest(
    std::optional<device::ConnectionFailureReason> failure_reason) {
  PerformFinishCurrentPairingRequest(
      failure_reason, base::Time::Now() - pairing_start_timestamp_);
  current_pairing_device_id_.clear();

  // |pair_device_callback_| can be null if |receiver_| has already been
  // disconnected before this method is invoked.
  if (!pair_device_callback_) {
    BLUETOOTH_LOG(EVENT)
        << "FinishCurrentPairingRequest() called with |pair_device_callback_| "
           "null, not running callback";
    return;
  }

  std::move(pair_device_callback_).Run(GetPairingResult(failure_reason));
}

void DevicePairingHandler::OnDelegateDisconnect() {
  BLUETOOTH_LOG(DEBUG) << "Delegate disconnected";

  // If the delegate disconnects and we have a pairing attempt, cancel the
  // pairing.
  if (!current_pairing_device_id_.empty()) {
    BLUETOOTH_LOG(EVENT) << "Delegate disconnected during pairing with "
                         << current_pairing_device_id_ << ", canceling pairing";
    CancelPairing();
  }

  delegate_.reset();
}

void DevicePairingHandler::PairDevice(
    const std::string& device_id,
    mojo::PendingRemote<mojom::DevicePairingDelegate> delegate,
    PairDeviceCallback callback) {
  BLUETOOTH_LOG(USER) << "Attempting to pair with device " << device_id;

  // In certain situations, such as when the pairing dialog is initiated from
  // both the system tray and ChromeOS settings, an active pairing request
  // might be underway when the pairDevice function is called. In such
  // instances, cancel the second pairing request. (See b/311813249)
  if (!current_pairing_device_id_.empty()) {
    std::move(callback).Run(mojom::PairingResult::kNonAuthFailure);
    return;
  }

  pairing_start_timestamp_ = base::Time::Now();
  pair_device_callback_ = std::move(callback);

  delegate_.reset();
  delegate_.Bind(std::move(delegate));
  delegate_.set_disconnect_handler(base::BindOnce(
      &DevicePairingHandler::OnDelegateDisconnect, base::Unretained(this)));

  // If Bluetooth is not enabled, fail immediately.
  if (!IsBluetoothEnabled()) {
    BLUETOOTH_LOG(ERROR) << "Pairing failed due to Bluetooth not being "
                         << "enabled, device identifier: " << device_id;
    FinishCurrentPairingRequest(
        device::ConnectionFailureReason::kBluetoothDisabled);
    return;
  }

  current_pairing_device_id_ = device_id;
  PerformPairDevice(current_pairing_device_id_);
}

void DevicePairingHandler::OnAdapterStateChanged() {
  if (IsBluetoothEnabled())
    return;

  if (current_pairing_device_id_.empty())
    return;

  // If Bluetooth disables while we are attempting to pair, cancel the pairing.
  BLUETOOTH_LOG(EVENT) << "Bluetooth disabled while attempting to pair with "
                       << current_pairing_device_id_ << ", canceling pairing";
  CancelPairing();
}

bool DevicePairingHandler::IsBluetoothEnabled() const {
  return adapter_state_controller_->GetAdapterState() ==
         mojom::BluetoothSystemState::kEnabled;
}

void DevicePairingHandler::FlushForTesting() {
  receiver_.FlushForTesting();  // IN-TEST
}

}  // namespace ash::bluetooth_config