chromium/ash/quick_pair/pairing/pairer_broker_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 "ash/quick_pair/pairing/pairer_broker_impl.h"

#include <memory>

#include "ash/constants/ash_features.h"
#include "ash/quick_pair/common/account_key_failure.h"
#include "ash/quick_pair/common/device.h"
#include "ash/quick_pair/common/fast_pair/fast_pair_metrics.h"
#include "ash/quick_pair/common/pair_failure.h"
#include "ash/quick_pair/common/protocol.h"
#include "ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client_lookup_impl.h"
#include "ash/quick_pair/fast_pair_handshake/fast_pair_handshake.h"
#include "ash/quick_pair/fast_pair_handshake/fast_pair_handshake_lookup.h"
#include "ash/quick_pair/pairing/fast_pair/fast_pair_pairer.h"
#include "ash/quick_pair/pairing/fast_pair/fast_pair_pairer_impl.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/scoped_refptr.h"
#include "components/cross_device/logging/logging.h"
#include "device/bluetooth/bluetooth_adapter.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"

namespace {

constexpr int kMaxFailureRetryCount = 3;
constexpr int kMaxNumHandshakeAttempts = 3;

// 1s delay after cancelling pairing was chosen to align with Android's Fast
// Pair implementation.
constexpr base::TimeDelta kCancelPairingRetryDelay = base::Seconds(1);

// 1s delay after handshake failure to allow failed handshake to tear down.
// TODO(b/265311455): implement handshake factory to handle retry logic.
constexpr base::TimeDelta kRetryHandshakeDelay = base::Seconds(1);

}  // namespace

namespace ash {
namespace quick_pair {

PairerBrokerImpl::PairerBrokerImpl() {
  device::BluetoothAdapterFactory::Get()->GetAdapter(base::BindOnce(
      &PairerBrokerImpl::OnGetAdapter, weak_pointer_factory_.GetWeakPtr()));
}

void PairerBrokerImpl::OnGetAdapter(
    scoped_refptr<device::BluetoothAdapter> adapter) {
  adapter_ = adapter;
}

PairerBrokerImpl::~PairerBrokerImpl() = default;

void PairerBrokerImpl::AddObserver(Observer* observer) {
  observers_.AddObserver(observer);
}

void PairerBrokerImpl::RemoveObserver(Observer* observer) {
  observers_.RemoveObserver(observer);
}

void PairerBrokerImpl::PairDevice(scoped_refptr<Device> device) {
  if (ash::features::IsFastPairBleRotationEnabled() &&
      device->protocol() == Protocol::kFastPairRetroactive &&
      model_id_to_current_ble_address_map_.contains(device->metadata_id()) &&
      model_id_to_current_ble_address_map_[device->metadata_id()] !=
          device->ble_address()) {
    // There is already an entry in the map for the same model id that we have,
    // see if a handshake has already been created for it as well.
    auto* handshake = FastPairHandshakeLookup::GetInstance()->Get(
        model_id_to_current_ble_address_map_[device->metadata_id()]);
    if (handshake) {
      CD_LOG(VERBOSE, Feature::FP)
          << __func__
          << ": A handshake already occurred for this device using a "
             "different BLE Address, setting the callback and returning.";

      // If there is already a handshake created for the device. Set the
      // callback so the flow associated with that device knows it should not
      // try to write the account and instead restart the pairing process.
      handshake->BleAddressRotated(
          base::BindOnce(&PairerBrokerImpl::OnBleAddressRotation,
                         weak_pointer_factory_.GetWeakPtr(), device));
      return;
    }
  }

  model_id_to_current_ble_address_map_.insert_or_assign(device->metadata_id(),
                                                        device->ble_address());
  did_handshake_previously_complete_successfully_map_.insert_or_assign(
      device->metadata_id(), false);
  PairFastPairDevice(std::move(device));
}

void PairerBrokerImpl::OnBleAddressRotation(scoped_refptr<Device> device) {
  // The BLE Address rotated, so we need to start the Retroactive Pairing
  // process over again after clearing the state.
  EraseHandshakeAndFromPairers(device);
  did_handshake_previously_complete_successfully_map_.insert_or_assign(
      device->metadata_id(), false);
  PairFastPairDevice(std::move(device));
}

void PairerBrokerImpl::EraseHandshakeAndFromPairers(
    scoped_refptr<Device> device) {
  // |fast_pair_pairers_| and its children objects depend on the handshake
  // instance. Shut them down before destroying the handshake. Also remove
  // the GATT connection.
  pair_failure_counts_.erase(device->metadata_id());
  fast_pair_pairers_.erase(device->metadata_id());
  FastPairHandshakeLookup::GetInstance()->Erase(device);
  did_handshake_previously_complete_successfully_map_.insert_or_assign(
      device->metadata_id(), false);
  FastPairGattServiceClientLookup::GetInstance()->Erase(
      adapter_->GetDevice(device->ble_address()));
}

bool PairerBrokerImpl::IsPairing() {
  // We are guaranteed to not be pairing when the following two maps are
  // empty.
  return !fast_pair_pairers_.empty() || !pair_failure_counts_.empty();
}

void PairerBrokerImpl::StopPairing() {
  fast_pair_pairers_.clear();
  pair_failure_counts_.clear();
}

void PairerBrokerImpl::PairFastPairDevice(scoped_refptr<Device> device) {
  if (base::Contains(fast_pair_pairers_, device->metadata_id())) {
    CD_LOG(WARNING, Feature::FP)
        << __func__ << ": Already pairing device" << device;
    RecordFastPairInitializePairingProcessEvent(
        *device, FastPairInitializePairingProcessEvent::kAlreadyPairingFailure);
    return;
  }

  // If this is a v1 pairing, we don't have to make a handshake before bonding
  // because we will pass off pairing to the classic Bluetooth pairing dialog in
  // 'FastPairPairer', so skip straight to 'StartBondingAttempt'.
  DCHECK(device->version().has_value());
  if (device->version().value() == DeviceFastPairVersion::kV1) {
    StartBondingAttempt(device);
    return;
  }

  // Otherwise, try to create a handshake.
  CreateHandshake(std::move(device));
}

void PairerBrokerImpl::CreateHandshake(scoped_refptr<Device> device) {
  if (ash::features::IsFastPairBleRotationEnabled() &&
      device->ble_address() !=
          model_id_to_current_ble_address_map_[device->metadata_id()]) {
    // If the current |device| has a different BLE Address than the address in
    // the map, abort creating the handshake and return early;
    CD_LOG(VERBOSE, Feature::FP)
        << __func__
        << ": The device's BLE did not match the expected value, returning.";
    return;
  }

  auto* fast_pair_handshake =
      FastPairHandshakeLookup::GetInstance()->Get(device);

  if (fast_pair_handshake) {
    if (fast_pair_handshake->completed_successfully()) {
      CD_LOG(VERBOSE, Feature::FP)
          << __func__ << ": Reusing existing handshake for pair attempt.";
      RecordFastPairInitializePairingProcessEvent(
          *device, FastPairInitializePairingProcessEvent::kHandshakeReused);
      StartBondingAttempt(device);
      return;
    } else {
      // If the previous handshake did not complete successfully, erase it
      // before attempting to create a new handshake for the device.
      FastPairHandshakeLookup::GetInstance()->Erase(device);
    }
  }

  CD_LOG(VERBOSE, Feature::FP)
      << __func__ << ": Creating new handshake for pair attempt.";
  num_handshake_attempts_[device->metadata_id()]++;
  FastPairHandshakeLookup::GetInstance()->Create(
      adapter_, device,
      base::BindOnce(&PairerBrokerImpl::OnHandshakeComplete,
                     weak_pointer_factory_.GetWeakPtr()));
}

void PairerBrokerImpl::OnHandshakeComplete(scoped_refptr<Device> device,
                                           std::optional<PairFailure> failure) {
  if (failure.has_value()) {
    CD_LOG(WARNING, Feature::FP) << __func__ << ": Handshake failed with "
                                 << device << " because: " << failure.value();
    OnHandshakeFailure(device, failure.value());
    return;
  }

  // During handshake, the device address can be set to null.
  if (!device->classic_address()) {
    CD_LOG(WARNING, Feature::FP)
        << __func__ << ": Device lost during handshake.";
    OnHandshakeFailure(device, PairFailure::kPairingDeviceLost);
    return;
  }

  if (!did_handshake_previously_complete_successfully_map_
          [device->metadata_id()]) {
    // Even if an observer does not implement this function in particular, it
    // will use the default implementation in the PairerBroker. The number
    // of observers is based on the number that call `AddObserver`, not by
    // the number that implement and override this function in their
    // derived class.
    for (auto& observer : observers_) {
      observer.OnHandshakeComplete(device);
    }

    did_handshake_previously_complete_successfully_map_.insert_or_assign(
        device->metadata_id(), true);
  }

  RecordEffectiveHandshakeSuccess(/*success=*/true);
  RecordHandshakeAttemptCount(num_handshake_attempts_[device->metadata_id()]);

  // Reset |num_handshake_attempts_| so if the handshake is lost during pairing,
  // we will attempt to create it 3 more times. This should be an extremely rare
  // situation, such as handshake happening directly before the device rotates
  // ble addresses.
  num_handshake_attempts_[device->metadata_id()] = 0;
  StartBondingAttempt(device);
}

void PairerBrokerImpl::OnHandshakeFailure(scoped_refptr<Device> device,
                                          PairFailure failure) {
  if (num_handshake_attempts_[device->metadata_id()] <
          kMaxNumHandshakeAttempts &&
      !ash::features::IsFastPairHandshakeLongTermRefactorEnabled()) {
    // Directly calling CreateHandshake() from here will cause the new
    // handshake to be nested inside the failed handshake. Use a timer to give
    // the failed handshake time to cleanup and avoid nesting.
    retry_handshake_timer_.Start(
        FROM_HERE, kRetryHandshakeDelay,
        base::BindOnce(&PairerBrokerImpl::CreateHandshake,
                       weak_pointer_factory_.GetWeakPtr(), device));
    return;
  }

  CD_LOG(VERBOSE, Feature::FP)
      << __func__ << ": Handshake failed to be created. Notifying observers.";
  RecordEffectiveHandshakeSuccess(/*success=*/false);
  RecordInitializationFailureReason(*device, failure);
  for (auto& observer : observers_) {
    observer.OnPairFailure(device, failure);
  }

  FastPairHandshakeLookup::GetInstance()->Erase(device);
  return;
}

void PairerBrokerImpl::StartBondingAttempt(scoped_refptr<Device> device) {
  if (!base::Contains(pair_failure_counts_, device->metadata_id())) {
    pair_failure_counts_[device->metadata_id()] = 0;

    // `OnPairingStart` is used in metrics to signal the beginning of the
    // initialization. We only want to signal this when pairing begins on the
    // first pairing attempt, otherwise observers will be notified on each retry
    // and incorrectly capture initialization in our metrics three times instead
    // of one when it begins.
    for (auto& observer : observers_) {
      observer.OnPairingStart(device);
    }
  }

  CD_LOG(VERBOSE, Feature::FP) << __func__ << ": " << device;

  DCHECK(adapter_);
  fast_pair_pairers_[device->metadata_id()] =
      FastPairPairerImpl::Factory::Create(
          adapter_, device,
          base::BindOnce(&PairerBrokerImpl::OnFastPairDeviceBonded,
                         weak_pointer_factory_.GetWeakPtr()),
          base::BindOnce(&PairerBrokerImpl::OnFastPairBondingFailure,
                         weak_pointer_factory_.GetWeakPtr()),
          base::BindOnce(&PairerBrokerImpl::OnAccountKeyFailure,
                         weak_pointer_factory_.GetWeakPtr()),
          base::BindOnce(&PairerBrokerImpl::OnFastPairProcedureComplete,
                         weak_pointer_factory_.GetWeakPtr()));
}

void PairerBrokerImpl::OnFastPairDeviceBonded(scoped_refptr<Device> device) {
  CD_LOG(VERBOSE, Feature::FP) << __func__ << ": Device=" << device;

  for (auto& observer : observers_) {
    observer.OnDevicePaired(device);
  }

  RecordPairFailureRetry(
      /*num_retries=*/pair_failure_counts_[device->metadata_id()]);
  pair_failure_counts_.erase(device->metadata_id());
}

void PairerBrokerImpl::OnFastPairBondingFailure(scoped_refptr<Device> device,
                                                PairFailure failure) {
  ++pair_failure_counts_[device->metadata_id()];
  CD_LOG(VERBOSE, Feature::FP)
      << __func__ << ": Device=" << device << ", Failure=" << failure
      << ", Failure Count = " << pair_failure_counts_[device->metadata_id()];

  device::BluetoothDevice* bt_device = nullptr;
  if (device->classic_address()) {
    bt_device = adapter_->GetDevice(device->classic_address().value());
  }

  if (pair_failure_counts_[device->metadata_id()] == kMaxFailureRetryCount) {
    CD_LOG(VERBOSE, Feature::FP)
        << __func__ << ": Reached max failure count. Notifying observers.";
    RecordProtocolPairingStep(FastPairProtocolPairingSteps::kExhaustedRetries,
                              *device);
    for (auto& observer : observers_) {
      observer.OnPairFailure(device, failure);
    }

    if (bt_device && !bt_device->IsPaired()) {
      bt_device->CancelPairing();
    }

    EraseHandshakeAndFromPairers(device);
    return;
  }

  fast_pair_pairers_.erase(device->metadata_id());

  if (bt_device && !bt_device->IsPaired()) {
    CD_LOG(VERBOSE, Feature::FP)
        << __func__
        << ": Cancelling pairing and scheduling retry for failed pair attempt.";
    bt_device->CancelPairing();

    // Create a timer to wait |kCancelPairingRetryDelay| after cancelling
    // pairing to retry the pairing attempt.
    cancel_pairing_timer_.Start(
        FROM_HERE, kCancelPairingRetryDelay,
        base::BindOnce(&PairerBrokerImpl::PairFastPairDevice,
                       weak_pointer_factory_.GetWeakPtr(), device));

    return;
  }

  PairFastPairDevice(device);
}

void PairerBrokerImpl::OnAccountKeyFailure(scoped_refptr<Device> device,
                                           AccountKeyFailure failure) {
  CD_LOG(VERBOSE, Feature::FP)
      << __func__ << ": Device=" << device << ", Failure=" << failure;

  for (auto& observer : observers_) {
    observer.OnAccountKeyWrite(device, failure);
  }

  EraseHandshakeAndFromPairers(device);
}

void PairerBrokerImpl::OnFastPairProcedureComplete(
    scoped_refptr<Device> device) {
  CD_LOG(VERBOSE, Feature::FP) << __func__ << ": Device=" << device;

  for (auto& observer : observers_) {
    observer.OnPairingComplete(device);
  }

  // If we get to this point in the flow for the initial and retroactive pairing
  // scenarios, this means that the account key has successfully been written
  // for devices with a version of V2 or higher.
  if (device->version().has_value() &&
      device->version().value() != DeviceFastPairVersion::kV1 &&
      (device->protocol() == Protocol::kFastPairInitial ||
       device->protocol() == Protocol::kFastPairRetroactive)) {
    for (auto& observer : observers_) {
      observer.OnAccountKeyWrite(device, /*error=*/std::nullopt);
    }
  }

  EraseHandshakeAndFromPairers(device);
}

}  // namespace quick_pair
}  // namespace ash