chromium/chromeos/ash/services/bluetooth_config/device_pairing_handler_impl.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_impl.h"

#include "base/task/single_thread_task_runner.h"
#include "chromeos/ash/services/bluetooth_config/device_conversion_util.h"
#include "components/device_event_log/device_event_log.h"

namespace ash::bluetooth_config {

namespace {

DevicePairingHandlerImpl::Factory* g_test_factory = nullptr;

device::BluetoothTransport GetBluetoothTransport(
    device::BluetoothTransport type) {
  switch (type) {
    case device::BLUETOOTH_TRANSPORT_CLASSIC:
      return device::BLUETOOTH_TRANSPORT_CLASSIC;
    case device::BLUETOOTH_TRANSPORT_LE:
      return device::BLUETOOTH_TRANSPORT_LE;
    case device::BLUETOOTH_TRANSPORT_DUAL:
      return device::BLUETOOTH_TRANSPORT_DUAL;
    default:
      return device::BLUETOOTH_TRANSPORT_INVALID;
  }
}

}  // namespace

// static
std::unique_ptr<DevicePairingHandler> DevicePairingHandlerImpl::Factory::Create(
    mojo::PendingReceiver<mojom::DevicePairingHandler> pending_receiver,
    AdapterStateController* adapter_state_controller,
    scoped_refptr<device::BluetoothAdapter> bluetooth_adapter,
    FastPairDelegate* fast_pair_delegate) {
  if (g_test_factory) {
    return g_test_factory->CreateInstance(
        std::move(pending_receiver), adapter_state_controller,
        bluetooth_adapter, fast_pair_delegate);
  }

  return base::WrapUnique(new DevicePairingHandlerImpl(
      std::move(pending_receiver), adapter_state_controller, bluetooth_adapter,
      fast_pair_delegate));
}

// static
void DevicePairingHandlerImpl::Factory::SetFactoryForTesting(
    Factory* test_factory) {
  g_test_factory = test_factory;
}

DevicePairingHandlerImpl::Factory::~Factory() = default;

// static
const base::TimeDelta DevicePairingHandlerImpl::kPairingFailureDelay =
    base::Milliseconds(500);

DevicePairingHandlerImpl::DevicePairingHandlerImpl(
    mojo::PendingReceiver<mojom::DevicePairingHandler> pending_receiver,
    AdapterStateController* adapter_state_controller,
    scoped_refptr<device::BluetoothAdapter> bluetooth_adapter,
    FastPairDelegate* fast_pair_delegate)
    : DevicePairingHandler(std::move(pending_receiver),
                           adapter_state_controller),
      bluetooth_adapter_(std::move(bluetooth_adapter)),
      fast_pair_delegate_(fast_pair_delegate) {}

DevicePairingHandlerImpl::~DevicePairingHandlerImpl() {
  // If we have a pairing attempt and this class is destroyed, cancel the
  // pairing.
  if (!current_pairing_device_id().empty()) {
    BLUETOOTH_LOG(EVENT)
        << "DevicePairingHandlerImpl is being destroyed while pairing with "
        << current_pairing_device_id() << ", canceling pairing";
    CancelPairing();
  }
}

void DevicePairingHandlerImpl::FetchDevice(const std::string& device_address,
                                           FetchDeviceCallback callback) {
  BLUETOOTH_LOG(EVENT) << "Fetching device with address: " << device_address;
  for (auto* device : bluetooth_adapter_->GetDevices()) {
    if (device->GetAddress() != device_address)
      continue;

    std::move(callback).Run(
        GenerateBluetoothDeviceMojoProperties(device, fast_pair_delegate_));
    return;
  }
  BLUETOOTH_LOG(ERROR) << "Device with address: " << device_address
                       << " was not found";
  std::move(callback).Run(std::move(nullptr));
}

void DevicePairingHandlerImpl::PerformPairDevice(const std::string& device_id) {
  // Find the device and attempt to pair to it.
  device::BluetoothDevice* device = FindDevice(device_id);

  if (!device) {
    BLUETOOTH_LOG(ERROR) << "Pairing failed due to device not being "
                         << "found, identifier: " << device_id;
    FinishCurrentPairingRequest(device::ConnectionFailureReason::kFailed);
    return;
  }

  device->Connect(
      /*delegate=*/this,
      base::BindOnce(&DevicePairingHandlerImpl::OnDeviceConnect,
                     weak_ptr_factory_.GetWeakPtr()));
}

void DevicePairingHandlerImpl::PerformFinishCurrentPairingRequest(
    std::optional<device::ConnectionFailureReason> failure_reason,
    base::TimeDelta duration) {
  // Reset state.
  is_canceling_pairing_ = false;
  device::BluetoothDevice* device = FindDevice(current_pairing_device_id());

  device::BluetoothTransport transport =
      device ? device->GetType()
             : device::BluetoothTransport::BLUETOOTH_TRANSPORT_INVALID;

  device::RecordPairingResult(failure_reason, GetBluetoothTransport(transport),
                              duration);
}

void DevicePairingHandlerImpl::CancelPairing() {
  is_canceling_pairing_ = true;
  device::BluetoothDevice* device = FindDevice(current_pairing_device_id());
  if (!device) {
    BLUETOOTH_LOG(ERROR)
        << "Could not cancel pairing for device to due device no longer being "
           "found, identifier: "
        << current_pairing_device_id();
    FinishCurrentPairingRequest(device::ConnectionFailureReason::kNotFound);
    return;
  }

  // Cancelling the active pairing attempt will cause OnDeviceConnect() to fire
  // with an error code.
  device->CancelPairing();
}

void DevicePairingHandlerImpl::OnRequestPinCode(const std::string& pin_code) {
  device::BluetoothDevice* device = FindDevice(current_pairing_device_id());
  if (!device) {
    BLUETOOTH_LOG(ERROR)
        << "OnRequestPinCode failed due to device no longer being "
        << "found, identifier: " << current_pairing_device_id();
    FinishCurrentPairingRequest(device::ConnectionFailureReason::kNotFound);
    return;
  }

  BLUETOOTH_LOG(USER) << "Received pin code " << pin_code << " for device "
                      << current_pairing_device_id();
  device->SetPinCode(pin_code);
}

void DevicePairingHandlerImpl::OnRequestPasskey(const std::string& passkey) {
  device::BluetoothDevice* device = FindDevice(current_pairing_device_id());
  if (!device) {
    BLUETOOTH_LOG(ERROR)
        << "OnRequestPasskey failed due to device no longer being "
        << "found, identifier: " << current_pairing_device_id();
    FinishCurrentPairingRequest(device::ConnectionFailureReason::kNotFound);
    return;
  }

  uint32_t passkey_num;
  if (base::StringToUint(passkey, &passkey_num)) {
    BLUETOOTH_LOG(USER) << "Received passkey " << passkey_num << " for device "
                        << current_pairing_device_id();
    device->SetPasskey(passkey_num);
    return;
  }

  // If string to uint32_t conversion was unsuccessful, cancel the pairing.
  BLUETOOTH_LOG(ERROR) << "Converting " << passkey
                       << "to uint32_t failed, canceling pairing with "
                       << current_pairing_device_id();
  CancelPairing();
}

void DevicePairingHandlerImpl::OnConfirmPairing(bool confirmed) {
  BLUETOOTH_LOG(EVENT) << "OnConfirmPairing() called with confirmed: "
                       << confirmed;

  device::BluetoothDevice* device = FindDevice(current_pairing_device_id());
  if (!device) {
    BLUETOOTH_LOG(ERROR)
        << "OnConfirmPairing failed due to device no longer being "
        << "found, identifier: " << current_pairing_device_id();
    FinishCurrentPairingRequest(device::ConnectionFailureReason::kNotFound);
    return;
  }

  if (confirmed) {
    device->ConfirmPairing();
  } else {
    device->CancelPairing();
  }
}

void DevicePairingHandlerImpl::RequestPinCode(device::BluetoothDevice* device) {
  DCHECK(device->GetIdentifier() == current_pairing_device_id());
  SendRequestPinCode();
}

void DevicePairingHandlerImpl::RequestPasskey(device::BluetoothDevice* device) {
  DCHECK(device->GetIdentifier() == current_pairing_device_id());
  SendRequestPasskey();
}

void DevicePairingHandlerImpl::DisplayPinCode(device::BluetoothDevice* device,
                                              const std::string& pin_code) {
  DCHECK(device->GetIdentifier() == current_pairing_device_id());
  SendDisplayPinCode(pin_code);
}

void DevicePairingHandlerImpl::DisplayPasskey(device::BluetoothDevice* device,
                                              uint32_t passkey) {
  DCHECK(device->GetIdentifier() == current_pairing_device_id());
  SendDisplayPasskey(passkey);
}

void DevicePairingHandlerImpl::KeysEntered(device::BluetoothDevice* device,
                                           uint32_t entered) {
  DCHECK(device->GetIdentifier() == current_pairing_device_id());
  SendKeysEntered(entered);
}

void DevicePairingHandlerImpl::ConfirmPasskey(device::BluetoothDevice* device,
                                              uint32_t passkey) {
  DCHECK(device->GetIdentifier() == current_pairing_device_id());
  SendConfirmPasskey(passkey);
}

void DevicePairingHandlerImpl::AuthorizePairing(
    device::BluetoothDevice* device) {
  DCHECK(device->GetIdentifier() == current_pairing_device_id());
  SendAuthorizePairing();
}

void DevicePairingHandlerImpl::OnDeviceConnect(
    std::optional<device::BluetoothDevice::ConnectErrorCode> error_code) {
  if (!error_code.has_value()) {
    BLUETOOTH_LOG(EVENT) << "Device " << current_pairing_device_id()
                         << " successfully paired";
    FinishCurrentPairingRequest(std::nullopt);
    return;
  }

  // In some cases, device->Connect() will return a failure if the pairing
  // succeeded but the subsequent connection request returns with a failure.
  // Empirically, it's found that the device actually does connect, and
  // device->IsConnected() returns true. Wait |kPairingFailureDelay| to check if
  // the device is connected. Only do this if the failure is not due to a
  // pairing cancellation. If the pairing is canceled, we know for sure that the
  // device is not actually paired.
  // TODO(b/209531279): Remove this delay and |is_canceling_pairing_| when the
  // root cause of issue is fixed.
  if (!is_canceling_pairing_) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE,
        base::BindOnce(&DevicePairingHandlerImpl::HandlePairingFailed,
                       weak_ptr_factory_.GetWeakPtr(), error_code.value()),
        kPairingFailureDelay);
    return;
  }

  // Immediately handle pairing failures if pairing is being canceled, because
  // we know for sure that the device is not actually paired, and because if
  // the pairing is being canceled due to the handler being destroyed, if there
  // is a delay the failure will never be handled.
  HandlePairingFailed(error_code.value());
}

void DevicePairingHandlerImpl::HandlePairingFailed(
    device::BluetoothDevice::ConnectErrorCode error_code) {
  device::BluetoothDevice* device = FindDevice(current_pairing_device_id());

  // In some cases, device->Connect() will return a failure if the pairing
  // succeeded but the subsequent connection request returns with a failure.
  // Empirically, it's found that the device actually does connect, and
  // device->IsConnected() returns true. Handle this case the
  // same as pairing succeeding if this wasn't a pairing cancellation.
  // TODO(b/209531279): Remove this when the root cause of issue is fixed.
  if (device && device->IsConnected() && !is_canceling_pairing_) {
    BLUETOOTH_LOG(EVENT)
        << device->GetAddress()
        << ": Pairing finished with an error code, but device "
        << "is connected. Handling like pairing succeeded. Error code: "
        << error_code;
    FinishCurrentPairingRequest(std::nullopt);
    return;
  }

  // We use |current_pairing_device_id()| since it conveys the same information
  // as the address and |device| could be |nullptr|.
  BLUETOOTH_LOG(ERROR) << current_pairing_device_id()
                       << ": Pairing failed with error code: " << error_code;

  FinishCurrentPairingRequest(GetConnectionFailureReason(error_code));
}

device::BluetoothDevice* DevicePairingHandlerImpl::FindDevice(
    const std::string& device_id) const {
  for (auto* device : bluetooth_adapter_->GetDevices()) {
    if (device->GetIdentifier() != device_id)
      continue;
    return device;
  }
  return nullptr;
}

}  // namespace ash::bluetooth_config