chromium/ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client_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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client_impl.h"

#include "ash/constants/ash_features.h"
#include "ash/quick_pair/common/constants.h"
#include "ash/quick_pair/common/fast_pair/fast_pair_metrics.h"
#include "ash/quick_pair/fast_pair_handshake/fast_pair_data_encryptor.h"
#include "base/memory/ptr_util.h"
#include "base/ranges/algorithm.h"
#include "base/time/time.h"
#include "components/cross_device/logging/logging.h"
#include "device/bluetooth/bluetooth_device.h"
#include "device/bluetooth/bluetooth_gatt_connection.h"
#include "device/bluetooth/bluetooth_gatt_notify_session.h"
#include "device/bluetooth/bluetooth_remote_gatt_characteristic.h"
#include "device/bluetooth/bluetooth_remote_gatt_service.h"
#include "device/bluetooth/public/cpp/bluetooth_address.h"
#include "third_party/boringssl/src/include/openssl/rand.h"

namespace {

// We have two UUID possibilities for each characteristic because they changed
// across different Fast Pair versions.
const device::BluetoothUUID kModelIDCharacteristicUuidV1("1233");
const device::BluetoothUUID kModelIDCharacteristicUuidV2(
    "FE2C1233-8366-4814-8EB0-01DE32100BEA");
const device::BluetoothUUID kKeyBasedCharacteristicUuidV1("1234");
const device::BluetoothUUID kKeyBasedCharacteristicUuidV2(
    "FE2C1234-8366-4814-8EB0-01DE32100BEA");
const device::BluetoothUUID kPasskeyCharacteristicUuidV1("1235");
const device::BluetoothUUID kPasskeyCharacteristicUuidV2(
    "FE2C1235-8366-4814-8EB0-01DE32100BEA");
const device::BluetoothUUID kAccountKeyCharacteristicUuidV1("1236");
const device::BluetoothUUID kAccountKeyCharacteristicUuidV2(
    "FE2C1236-8366-4814-8EB0-01DE32100BEA");
const device::BluetoothUUID kAdditionalDataCharacteristicUuidV1("1237");
const device::BluetoothUUID kAdditionalDataCharacteristicUuidV2(
    "FE2C1237-8366-4814-8EB0-01DE32100BEA");

constexpr uint8_t kProviderAddressStartIndex = 2;
constexpr uint8_t kSeekerAddressStartIndex = 8;
constexpr uint8_t kSeekerPasskey = 0x02;
constexpr uint8_t kAccountKeyStartByte = 0x04;

constexpr uint8_t kEmptyFlags = 0x00;

// Action Request constants.
constexpr uint8_t kActionRequestDeviceActionFlags = 0x80;
constexpr uint8_t kActionRequestAdditionalDataFlags = 0x40;
constexpr uint8_t kActionMessage = 0x10;
constexpr size_t kMessageTypeIndex = 0;
constexpr size_t kFlagsIndex = 1;
constexpr size_t kMessageGroupIndex = 8;
constexpr size_t kMessageCodeIndex = 9;
constexpr size_t kDataIdOrSizeIndex = 10;
constexpr size_t kAdditionalDataStartIndex = 11;
constexpr uint8_t kAdditionalDataMaxSizeBytes = 5;

// Personalized Name data request constants.
constexpr uint8_t kPersonalizedNameDataId = 0x10;

constexpr base::TimeDelta kGattOperationTimeout = base::Seconds(15);
constexpr int kMaxNumGattConnectionAttempts = 3;
constexpr base::TimeDelta kCoolOffPeriodBeforeGattConnectionAfterDisconnect =
    base::Seconds(2);
constexpr base::TimeDelta kDisconnectResponseTimeout = base::Seconds(5);

constexpr const char* ErrorCodeToString(
    device::BluetoothGattService::GattErrorCode error_code) {
  switch (error_code) {
    case device::BluetoothGattService::GattErrorCode::kUnknown:
      return "GATT_ERROR_UNKNOWN";
    case device::BluetoothGattService::GattErrorCode::kFailed:
      return "GATT_ERROR_FAILED";
    case device::BluetoothGattService::GattErrorCode::kInProgress:
      return "GATT_ERROR_IN_PROGRESS";
    case device::BluetoothGattService::GattErrorCode::kInvalidLength:
      return "GATT_ERROR_INVALID_LENGTH";
    case device::BluetoothGattService::GattErrorCode::kNotPermitted:
      return "GATT_ERROR_NOT_PERMITTED";
    case device::BluetoothGattService::GattErrorCode::kNotAuthorized:
      return "GATT_ERROR_NOT_AUTHORIZED";
    case device::BluetoothGattService::GattErrorCode::kNotPaired:
      return "GATT_ERROR_NOT_PAIRED";
    case device::BluetoothGattService::GattErrorCode::kNotSupported:
      return "GATT_ERROR_NOT_SUPPORTED";
    default:
      NOTREACHED();
  }
}

constexpr ash::quick_pair::AccountKeyFailure GattErrorCodeToAccountKeyFailure(
    device::BluetoothGattService::GattErrorCode error_code) {
  switch (error_code) {
    case device::BluetoothGattService::GattErrorCode::kUnknown:
      return ash::quick_pair::AccountKeyFailure::kGattErrorUnknown;
    case device::BluetoothGattService::GattErrorCode::kFailed:
      return ash::quick_pair::AccountKeyFailure::kGattErrorFailed;
    case device::BluetoothGattService::GattErrorCode::kInProgress:
      return ash::quick_pair::AccountKeyFailure::kGattInProgress;
    case device::BluetoothGattService::GattErrorCode::kInvalidLength:
      return ash::quick_pair::AccountKeyFailure::kGattErrorInvalidLength;
    case device::BluetoothGattService::GattErrorCode::kNotPermitted:
      return ash::quick_pair::AccountKeyFailure::kGattErrorNotPermitted;
    case device::BluetoothGattService::GattErrorCode::kNotAuthorized:
      return ash::quick_pair::AccountKeyFailure::kGattErrorNotAuthorized;
    case device::BluetoothGattService::GattErrorCode::kNotPaired:
      return ash::quick_pair::AccountKeyFailure::kGattErrorNotPaired;
    case device::BluetoothGattService::GattErrorCode::kNotSupported:
      return ash::quick_pair::AccountKeyFailure::kGattErrorNotSupported;
    default:
      NOTREACHED();
  }
}

constexpr const char* ErrorCodeToString(
    device::BluetoothDevice::ConnectErrorCode error_code) {
  switch (error_code) {
    case device::BluetoothDevice::ConnectErrorCode::ERROR_AUTH_CANCELED:
      return "ERROR_AUTH_CANCELED";
    case device::BluetoothDevice::ConnectErrorCode::ERROR_AUTH_FAILED:
      return "ERROR_AUTH_FAILED";
    case device::BluetoothDevice::ConnectErrorCode::ERROR_AUTH_REJECTED:
      return "ERROR_AUTH_REJECTED";
    case device::BluetoothDevice::ConnectErrorCode::ERROR_AUTH_TIMEOUT:
      return "ERROR_AUTH_TIMEOUT";
    case device::BluetoothDevice::ConnectErrorCode::ERROR_FAILED:
      return "ERROR_FAILED";
    case device::BluetoothDevice::ConnectErrorCode::ERROR_INPROGRESS:
      return "ERROR_INPROGRESS";
    case device::BluetoothDevice::ConnectErrorCode::ERROR_UNKNOWN:
      return "ERROR_UNKNOWN";
    case device::BluetoothDevice::ConnectErrorCode::ERROR_UNSUPPORTED_DEVICE:
      return "ERROR_UNSUPPORTED_DEVICE";
    default:
      NOTREACHED();
  }
}

// *** Prefer to use CreateActionRequestBeforeAdditionalData or create analogous
// function for requesting device action using `kActionRequestDeviceActionFlag`.
// ***
//
// Creates Action Request data array based on Table 2.2 of the Fast Pair spec:
// https://developers.google.com/nearby/fast-pair/specifications/characteristics#table1.2.2.
// Note: if the intended `additional_data` vector will have size 0, it is
// sufficient to pass a `nullopt` instead of an empty vector.
const std::array<uint8_t, kBlockByteSize> CreateActionRequest(
    const uint8_t flags,
    const std::string& provider_address,
    std::optional<const uint8_t> message_group,
    std::optional<const uint8_t> message_code,
    std::optional<const uint8_t> data_id_or_size,
    std::optional<const std::vector<uint8_t>> additional_data) {
  std::array<uint8_t, kBlockByteSize> request;

  request[kMessageTypeIndex] = kActionMessage;
  request[kFlagsIndex] = flags;

  // Copy provider address bytes.
  std::array<uint8_t, 6> provider_address_bytes;
  device::ParseBluetoothAddress(provider_address, provider_address_bytes);
  base::ranges::copy(
      provider_address_bytes,
      std::next(std::begin(request), kProviderAddressStartIndex));

  // Construct `request` based on `flags`. Optional values CHECKed for are
  // required based on the flag case.
  switch (flags) {
    case kEmptyFlags:
      break;
    case kActionRequestDeviceActionFlags:
      CHECK(message_group.has_value());
      CHECK(message_code.has_value());
      CHECK(data_id_or_size.has_value());

      request[kMessageGroupIndex] = message_group.value();
      request[kMessageCodeIndex] = message_code.value();

      if (additional_data.has_value()) {
        CHECK(additional_data.value().size() <= kAdditionalDataMaxSizeBytes);
        CHECK(data_id_or_size.value() == additional_data.value().size());
        base::ranges::copy(
            additional_data.value(),
            std::next(std::begin(request), kAdditionalDataStartIndex));
      } else {
        CHECK(data_id_or_size.value() == 0);
      }
      ABSL_FALLTHROUGH_INTENDED;
    case kActionRequestAdditionalDataFlags:
      CHECK(data_id_or_size.has_value());

      request[kDataIdOrSizeIndex] = data_id_or_size.value();
      break;
    default:
      NOTREACHED();
  }

  // Fill unused trailing bytes with random (salt) values.
  if (flags == kActionRequestDeviceActionFlags) {
    // `data_id_or_size` will contain the additional data size in this case.
    if (data_id_or_size.value() < kAdditionalDataMaxSizeBytes) {
      RAND_bytes(request.data() + kAdditionalDataStartIndex +
                     additional_data.value().size(),
                 kAdditionalDataMaxSizeBytes - data_id_or_size.value());
    }
  } else {
    RAND_bytes(request.data() + kAdditionalDataStartIndex,
               kAdditionalDataMaxSizeBytes);
  }

  return request;
}

// Creates Action Request data array with flag 0x40 based on Table 2.2 of the
// Fast Pair spec:
// https://developers.google.com/nearby/fast-pair/specifications/characteristics#table1.2.2.
// The flag indicates that an Additional Data packet write will follow this
// request.
const std::array<uint8_t, kBlockByteSize>
CreateActionRequestBeforeAdditionalData(const std::string& provider_address) {
  return CreateActionRequest(kActionRequestAdditionalDataFlags,
                             provider_address, std::nullopt, std::nullopt,
                             kPersonalizedNameDataId, std::nullopt);
}

}  // namespace

namespace ash {
namespace quick_pair {

// static
FastPairGattServiceClientImpl::Factory*
    FastPairGattServiceClientImpl::Factory::g_test_factory_ = nullptr;

// static
std::unique_ptr<FastPairGattServiceClient>
FastPairGattServiceClientImpl::Factory::Create(
    device::BluetoothDevice* device,
    scoped_refptr<device::BluetoothAdapter> adapter,
    base::OnceCallback<void(std::optional<PairFailure>)>
        on_initialized_callback) {
  if (g_test_factory_) {
    return g_test_factory_->CreateInstance(device, adapter,
                                           std::move(on_initialized_callback));
  }
  auto gatt_service = base::WrapUnique(new FastPairGattServiceClientImpl(
      device, adapter, std::move(on_initialized_callback)));
  if (ash::features::IsFastPairHandshakeLongTermRefactorEnabled()) {
    RecordGattInitializationStep(
        FastPairGattConnectionSteps::kConnectionStarted);
    gatt_service->AttemptGattConnection();
  }
  return gatt_service;
}

// static
void FastPairGattServiceClientImpl::Factory::SetFactoryForTesting(
    Factory* g_test_factory) {
  g_test_factory_ = g_test_factory;
}

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

FastPairGattServiceClientImpl::FastPairGattServiceClientImpl(
    device::BluetoothDevice* device,
    scoped_refptr<device::BluetoothAdapter> adapter,
    base::OnceCallback<void(std::optional<PairFailure>)>
        on_initialized_callback)
    : on_initialized_callback_(std::move(on_initialized_callback)),
      device_address_(device->GetAddress()),
      adapter_(std::move(adapter)) {
  adapter_observation_.Observe(adapter_.get());

  if (!ash::features::IsFastPairHandshakeLongTermRefactorEnabled()) {
    CD_LOG(INFO, Feature::FP)
        << __func__ << ": Starting the GATT connection to device";
    RecordGattInitializationStep(
        FastPairGattConnectionSteps::kConnectionStarted);
    AttemptGattConnection();
  }
}

FastPairGattServiceClientImpl::~FastPairGattServiceClientImpl() = default;

void FastPairGattServiceClientImpl::AttemptGattConnection() {
  CD_LOG(INFO, Feature::FP) << __func__;

  if (num_gatt_connection_attempts_ == kMaxNumGattConnectionAttempts) {
    NotifyInitializedError(PairFailure::kCreateGattConnection);
    RecordEffectiveGattConnectionSuccess(/*success=*/false);
    return;
  }

  num_gatt_connection_attempts_++;

  CD_LOG(INFO, Feature::FP)
      << __func__ << ": Starting GATT connection attempt #"
      << num_gatt_connection_attempts_ << " to device";

  // Attempt creating a GATT connection with the device.
  auto* device = adapter_->GetDevice(device_address_);
  if (!device) {
    // The device must have been lost between connection attempts.
    NotifyInitializedError(
        PairFailure::kPairingDeviceLostBetweenGattConnectionAttempts);
    return;
  }

  // If we are already bonded and connected, potentially due to attempting to
  // retroactive pair, don't disconnect the device since the device is already
  // in a good working state.
  if (device->IsBonded() && device->IsConnected()) {
    CD_LOG(INFO, Feature::FP)
        << __func__ << ": Device already bonded and connected";
    CreateGattConnection();
    return;
  }

  // Remove any pre-existing GATT connection on the device before we make a
  // new one. We cannot determine if there is a GATT connection already
  // established, and because its not very expensive and has no impact if there
  // is no connection established, we call `Disconnect` regardless.
  CD_LOG(INFO, Feature::FP)
      << __func__ << ": Disconnecting any previous connections before attempt";

  // Start a timer so if we don't get a response from the disconnect call, we
  // still proceed with GATT connection attempts.
  gatt_disconnect_timer_.Start(
      FROM_HERE, kDisconnectResponseTimeout,
      base::BindOnce(&FastPairGattServiceClientImpl::OnDisconnectTimeout,
                     weak_ptr_factory_.GetWeakPtr()));

  device->Disconnect(
      base::BindOnce(
          &FastPairGattServiceClientImpl::CoolOffBeforeCreateGattConnection,
          weak_ptr_factory_.GetWeakPtr()),
      base::BindOnce(&FastPairGattServiceClientImpl::NotifyInitializedError,
                     weak_ptr_factory_.GetWeakPtr(),
                     PairFailure::kFailureToDisconnectGattBetweenRetries));
}

void FastPairGattServiceClientImpl::CoolOffBeforeCreateGattConnection() {
  CD_LOG(INFO, Feature::FP) << __func__;

  if (!gatt_disconnect_timer_.IsRunning()) {
    // The disconnect has already timed out so return early here.
    CD_LOG(INFO, Feature::FP)
        << __func__ << ": Returning early due to prior disconnect timeout.";
    return;
  }

  // A response to the disconnect call was received, stop the associated timer.
  gatt_disconnect_timer_.Stop();

  // In order for the Disconnect to propagate into the platform code, we need
  // a cool off period before we attempt to create a GATT connection in the case
  // we did disconnect an active GATT connection.
  gatt_connect_after_disconnect_cool_off_timer_.Start(
      FROM_HERE, kCoolOffPeriodBeforeGattConnectionAfterDisconnect,
      base::BindOnce(&FastPairGattServiceClientImpl::CreateGattConnection,
                     weak_ptr_factory_.GetWeakPtr()));
}

void FastPairGattServiceClientImpl::OnDisconnectTimeout() {
  // This PairFailure is not surfaced to consumers on the
  // `on_initialized_callback_` because we retry the GATT connection. We don't
  // want the consumers to retry a FastPairHandshake before retries are
  // complete so we log the failure here and continue with the retry if we
  // haven't maxed out yet.
  CD_LOG(INFO, Feature::FP)
      << __func__ << ": reattempting after GATT disconnect timeout: "
      << PairFailure::kDisconnectResponseTimeout;
  RecordGattRetryFailureReason(PairFailure::kDisconnectResponseTimeout);
  AttemptGattConnection();
}

void FastPairGattServiceClientImpl::CreateGattConnection() {
  CD_LOG(INFO, Feature::FP) << __func__;

  // Attempt creating a GATT connection with the device.
  auto* device = adapter_->GetDevice(device_address_);
  if (!device) {
    // The device must have been lost between connection attempts.
    NotifyInitializedError(
        PairFailure::kPairingDeviceLostBetweenGattConnectionAttempts);
    return;
  }
  gatt_service_discovery_start_time_ = base::TimeTicks::Now();
  gatt_service_discovery_timer_.Start(
      FROM_HERE, kGattOperationTimeout,
      base::BindOnce(
          &FastPairGattServiceClientImpl::OnGattServiceDiscoveryTimeout,
          weak_ptr_factory_.GetWeakPtr()));

  device->CreateGattConnection(
      base::BindOnce(&FastPairGattServiceClientImpl::OnGattConnection,
                     weak_ptr_factory_.GetWeakPtr(), base::TimeTicks::Now()),
      kFastPairBluetoothUuid);
}

void FastPairGattServiceClientImpl::OnGattServiceDiscoveryTimeout() {
  // This PairFailure is not surfaced to consumers on the
  // `on_initialized_callback_` because we retry the GATT connection. We don't
  // want the consumers to retry a FastPairHandshake before retries are
  // complete so we log the failure here and continue with the retry if we
  // haven't maxed out yet.
  CD_LOG(INFO, Feature::FP)
      << __func__ << ": reattempting from previous GATT connection failure: "
      << PairFailure::kGattServiceDiscoveryTimeout;
  RecordGattRetryFailureReason(PairFailure::kGattServiceDiscoveryTimeout);
  AttemptGattConnection();
}

void FastPairGattServiceClientImpl::OnGattConnection(
    base::TimeTicks gatt_connection_start_time,
    std::unique_ptr<device::BluetoothGattConnection> gatt_connection,
    std::optional<device::BluetoothDevice::ConnectErrorCode> error_code) {
  if (error_code) {
    CD_LOG(WARNING, Feature::FP) << "Error creating GATT connection to device: "
                                 << ErrorCodeToString(error_code.value());
    RecordGattConnectionErrorCode(error_code.value());
    RecordGattConnectionResult(/*success=*/false);

    // This PairFailure is not surfaced to consumers on the
    // `on_initialized_callback_` because we retry the GATT connection. We don't
    // want the consumers to retry a FastPairHandshake before retries are
    // complete so we log the failure here and continue with the retry if we
    // haven't maxed out yet.
    CD_LOG(INFO, Feature::FP)
        << __func__ << ": reattempting from previous GATT connection failure: "
        << PairFailure::kBluetoothDeviceFailureCreatingGattConnection;
    RecordGattRetryFailureReason(
        PairFailure::kBluetoothDeviceFailureCreatingGattConnection);
    AttemptGattConnection();
  } else {
    CD_LOG(INFO, Feature::FP)
        << __func__ << ": Successful creation of GATT connection to device";
    RecordGattConnectionResult(/*success=*/true);
    RecordEffectiveGattConnectionSuccess(/*success=*/true);
    RecordGattConnectionAttemptCount(num_gatt_connection_attempts_);
    RecordTotalGattConnectionTime(base::TimeTicks::Now() -
                                  gatt_connection_start_time);
    gatt_connection_ = std::move(gatt_connection);
  }
}

void FastPairGattServiceClientImpl::ClearCurrentState() {
  adapter_.reset();
  adapter_observation_.Reset();
  gatt_connection_.reset();
  StopAllWriteRequestTimers();
  gatt_service_ = nullptr;
  account_key_characteristic_ = nullptr;
  key_based_characteristic_ = nullptr;
  passkey_characteristic_ = nullptr;
  gatt_service_discovery_timer_.Stop();
  gatt_disconnect_timer_.Stop();
  passkey_notify_session_timer_.Stop();
  keybased_notify_session_timer_.Stop();
  key_based_notify_session_.reset();
  passkey_notify_session_.reset();
}

void FastPairGattServiceClientImpl::NotifyInitializedError(
    PairFailure failure) {
  CD_LOG(VERBOSE, Feature::FP) << __func__ << failure;
  ClearCurrentState();

  // This function is invoked in several flows and it is possible for it to run
  // twice. In that case, we are ok with the first instance being the one that
  // reports the failure. An example is if we timeout waiting for all notify
  // sessions to start.
  if (on_initialized_callback_) {
    CD_LOG(VERBOSE, Feature::FP)
        << __func__ << "Executing initialized callback";
    std::move(on_initialized_callback_).Run(failure);
  }
}

void FastPairGattServiceClientImpl::NotifyWriteRequestError(
    PairFailure failure) {
  StopWriteRequestTimer(key_based_characteristic_);

  // |key_based_write_response_callback_| should always exist here.
  // If |OnWriteRequestError| is used to notify error before the timer
  // expires, the timer will be stopped in the line above. If the timer fires
  // after |key_based_write_response_callback_| is called below, |this| will be
  // torn down before |key_based_write_response_callback_| can be called again.
  DCHECK(key_based_write_response_callback_);
  std::move(key_based_write_response_callback_)
      .Run(/*response_data=*/{}, failure);
}

void FastPairGattServiceClientImpl::NotifyWritePasskeyError(
    PairFailure failure) {
  StopWriteRequestTimer(passkey_characteristic_);

  // |passkey_write_response_callback_| should always exist here.
  // If |OnWritePasskeyError| is used to notify error before the timer
  // expires, the timer will be stopped in the line above. If the timer fires
  // after |passkey_write_response_callback_| is called below, |this| will be
  // torn down before |passkey_write_response_callback_| can be called again.
  DCHECK(passkey_write_response_callback_);
  std::move(passkey_write_response_callback_)
      .Run(/*response_data=*/{}, failure);
}

void FastPairGattServiceClientImpl::NotifyWriteAccountKeyError(
    ash::quick_pair::AccountKeyFailure failure) {
  StopWriteRequestTimer(account_key_characteristic_);
  DCHECK(write_account_key_callback_);
  std::move(write_account_key_callback_).Run(failure);
}

void FastPairGattServiceClientImpl::GattDiscoveryCompleteForService(
    device::BluetoothAdapter* adapter,
    device::BluetoothRemoteGattService* service) {
  // Verify that the discovered service and device are the ones we care about.
  if (service->GetUUID() == kFastPairBluetoothUuid &&
      service->GetDevice()->GetAddress() == device_address_) {
    RecordGattServiceDiscoveryTime(base::TimeTicks::Now() -
                                   gatt_service_discovery_start_time_);
    gatt_service_discovery_timer_.Stop();
    CD_LOG(INFO, Feature::FP)
        << __func__ << ": Completed discovery for Fast Pair GATT service";
    RecordGattInitializationStep(FastPairGattConnectionSteps::kConnectionReady);
    gatt_service_ = service;
    auto pair_failure = SetGattCharacteristics();

    if (pair_failure.has_value()) {
      NotifyInitializedError(pair_failure.value());
    } else if (on_initialized_callback_) {
      is_initialized_ = true;
      RecordGattInitializationStep(
          FastPairGattConnectionSteps::kConnectionEstablished);
      std::move(on_initialized_callback_).Run(/*failure=*/std::nullopt);
    }
  }
}

std::vector<device::BluetoothRemoteGattCharacteristic*>
FastPairGattServiceClientImpl::GetCharacteristicsByUUIDs(
    const device::BluetoothUUID& uuidV1,
    const device::BluetoothUUID& uuidV2) {
  if (!gatt_service_)
    return {};

  // Default to V2 device to match Android implementation.
  std::vector<device::BluetoothRemoteGattCharacteristic*> characteristics =
      gatt_service_->GetCharacteristicsByUUID(uuidV2);

  characteristics = characteristics.size()
                        ? characteristics
                        : gatt_service_->GetCharacteristicsByUUID(uuidV1);
  return characteristics;
}

std::optional<PairFailure>
FastPairGattServiceClientImpl::SetGattCharacteristics() {
  auto key_based_characteristics = GetCharacteristicsByUUIDs(
      kKeyBasedCharacteristicUuidV1, kKeyBasedCharacteristicUuidV2);
  if (key_based_characteristics.empty()) {
    return PairFailure::kKeyBasedPairingCharacteristicDiscovery;
  }
  key_based_characteristic_ = key_based_characteristics[0];

  RecordGattInitializationStep(
      FastPairGattConnectionSteps::kFoundKeybasedPairingCharacteristic);

  auto passkey_characteristics = GetCharacteristicsByUUIDs(
      kPasskeyCharacteristicUuidV1, kPasskeyCharacteristicUuidV2);
  if (passkey_characteristics.empty()) {
    return PairFailure::kPasskeyCharacteristicDiscovery;
  }
  passkey_characteristic_ = passkey_characteristics[0];

  auto account_key_characteristics = GetCharacteristicsByUUIDs(
      kAccountKeyCharacteristicUuidV1, kAccountKeyCharacteristicUuidV2);
  if (account_key_characteristics.empty()) {
    return PairFailure::kAccountKeyCharacteristicDiscovery;
  }

  // The account key characteristic does not notify so unlike the other
  // characteristics set in this function, we will not need to create a notify
  // session for it later.
  account_key_characteristic_ = account_key_characteristics[0];

  // The model ID characteristic is required for retroactive pairing for BLE HID
  // devices
  auto model_id_characteristics = GetCharacteristicsByUUIDs(
      kModelIDCharacteristicUuidV1, kModelIDCharacteristicUuidV2);
  if (model_id_characteristics.empty()) {
    CD_LOG(WARNING, Feature::FP)
        << __func__ << ": Failed to discover Model ID characteristic.";
  } else {
    model_id_characteristic_ = model_id_characteristics[0];
  }

  auto additional_data_characteristics = GetCharacteristicsByUUIDs(
      kAdditionalDataCharacteristicUuidV1, kAdditionalDataCharacteristicUuidV2);

  // Failure not returned on failure to discover Additional Data characteristic
  // because it shouldn't interrupt the pairing flow. This achieves parity with
  // Android.
  if (additional_data_characteristics.empty()) {
    CD_LOG(WARNING, Feature::FP)
        << __func__ << ": Failed to discover Additional Data Characteristic.";
    return std::nullopt;
  }
  additional_data_characteristic_ = additional_data_characteristics[0];

  // No failure.
  return std::nullopt;
}

void FastPairGattServiceClientImpl::OnKeyBasedRequestNotifySession(
    const std::vector<uint8_t>& request_data,
    std::unique_ptr<device::BluetoothGattNotifySession> session) {
  RecordKeyBasedNotifyTime(base::TimeTicks::Now() -
                           keybased_notify_session_start_time_);
  keybased_notify_session_timer_.Stop();
  notify_keybased_start_time_ = base::TimeTicks::Now();

  // The session member variable is set to keep the session from going out of
  // scope and being destroyed.
  key_based_notify_session_ = std::move(session);

  key_based_write_request_start_time_ = base::TimeTicks::Now();

  WriteGattCharacteristicWithTimeout(
      key_based_characteristic_, request_data,
      device::BluetoothRemoteGattCharacteristic::WriteType::kWithResponse,
      base::BindOnce(&FastPairGattServiceClientImpl::NotifyWriteRequestError,
                     weak_ptr_factory_.GetWeakPtr(),
                     PairFailure::kKeyBasedPairingResponseTimeout),
      base::BindOnce(&FastPairGattServiceClientImpl::OnWriteRequest,
                     weak_ptr_factory_.GetWeakPtr()),
      base::BindOnce(&FastPairGattServiceClientImpl::OnWriteRequestError,
                     weak_ptr_factory_.GetWeakPtr()));
}

void FastPairGattServiceClientImpl::OnPasskeyNotifySession(
    const std::vector<uint8_t>& passkey_data,
    std::unique_ptr<device::BluetoothGattNotifySession> session) {
  RecordPasskeyNotifyTime(base::TimeTicks::Now() -
                          passkey_notify_session_start_time_);
  passkey_notify_session_timer_.Stop();
  notify_passkey_start_time_ = base::TimeTicks::Now();

  // The session member variable is set to keep the session from going out of
  // scope and being destroyed.
  passkey_notify_session_ = std::move(session);

  RecordGattInitializationStep(
      FastPairGattConnectionSteps::kNotifiationsEnabledForKeybasedPairing);

  passkey_write_request_start_time_ = base::TimeTicks::Now();

  WriteGattCharacteristicWithTimeout(
      passkey_characteristic_, passkey_data,
      device::BluetoothRemoteGattCharacteristic::WriteType::kWithResponse,
      base::BindOnce(&FastPairGattServiceClientImpl::NotifyWritePasskeyError,
                     weak_ptr_factory_.GetWeakPtr(),
                     PairFailure::kPasskeyResponseTimeout),
      base::BindOnce(&FastPairGattServiceClientImpl::OnWritePasskey,
                     weak_ptr_factory_.GetWeakPtr()),
      base::BindOnce(&FastPairGattServiceClientImpl::OnWritePasskeyError,
                     weak_ptr_factory_.GetWeakPtr()));
}

void FastPairGattServiceClientImpl::OnNotifySessionError(
    PairFailure failure,
    device::BluetoothGattService::GattErrorCode error) {
  if (failure == PairFailure::kKeyBasedPairingCharacteristicNotifySession) {
    CD_LOG(INFO, Feature::FP)
        << __func__
        << ": for key based characteristic: " << ErrorCodeToString(error);
    NotifyWriteRequestError(failure);
  } else if (failure == PairFailure::kPasskeyCharacteristicNotifySession) {
    CD_LOG(INFO, Feature::FP)
        << __func__
        << ": for passkey characteristic: " << ErrorCodeToString(error);
    NotifyWritePasskeyError(failure);
  } else {
    NOTREACHED();
  }
}

device::BluetoothRemoteGattService*
FastPairGattServiceClientImpl::gatt_service() {
  return gatt_service_;
}

const std::array<uint8_t, kBlockByteSize>
FastPairGattServiceClientImpl::CreateRequest(
    uint8_t message_type,
    uint8_t flags,
    const std::string& provider_address,
    const std::string& seekers_address) {
  std::array<uint8_t, kBlockByteSize> data_to_write;
  RAND_bytes(data_to_write.data(), kBlockByteSize);

  data_to_write[0] = message_type;
  data_to_write[1] = flags;

  std::array<uint8_t, 6> provider_address_bytes;
  device::ParseBluetoothAddress(provider_address, provider_address_bytes);
  base::ranges::copy(provider_address_bytes,
                     std::begin(data_to_write) + kProviderAddressStartIndex);

  // Seekers address can be empty, in which we would just have the bytes be
  // the salt.
  if (!seekers_address.empty()) {
    std::array<uint8_t, 6> seeker_address_bytes;
    device::ParseBluetoothAddress(seekers_address, seeker_address_bytes);
    base::ranges::copy(seeker_address_bytes,
                       std::begin(data_to_write) + kSeekerAddressStartIndex);
  }

  return data_to_write;
}

const std::array<uint8_t, kBlockByteSize>
FastPairGattServiceClientImpl::CreatePasskeyBlock(uint8_t message_type,
                                                  uint32_t passkey) {
  std::array<uint8_t, kBlockByteSize> data_to_write;
  RAND_bytes(data_to_write.data(), kBlockByteSize);

  data_to_write[0] = message_type;

  // Need to convert the uint_32 to uint_8 to use in our data vector.
  data_to_write[1] = (passkey & 0x00ff0000) >> 16;
  data_to_write[2] = (passkey & 0x0000ff00) >> 8;
  data_to_write[3] = passkey & 0x000000ff;
  return data_to_write;
}

bool FastPairGattServiceClientImpl::IsConnected() {
  return gatt_connection_ && gatt_connection_->IsConnected();
}

void FastPairGattServiceClientImpl::ReadModelIdAsync(
    base::OnceCallback<void(
        std::optional<device::BluetoothGattService::GattErrorCode> error_code,
        const std::vector<uint8_t>& value)> callback) {
  DCHECK(is_initialized_);

  if (!model_id_characteristic_) {
    std::move(callback).Run(
        device::BluetoothGattService::GattErrorCode::kNotSupported,
        std::vector<uint8_t>{});
    return;
  }

  model_id_characteristic_->ReadRemoteCharacteristic(std::move(callback));
}

void FastPairGattServiceClientImpl::WriteRequestAsync(
    uint8_t message_type,
    uint8_t flags,
    const std::string& provider_address,
    const std::string& seekers_address,
    FastPairDataEncryptor* fast_pair_data_encryptor,
    base::OnceCallback<void(std::vector<uint8_t>, std::optional<PairFailure>)>
        write_response_callback) {
  DCHECK(is_initialized_);
  DCHECK(!key_based_write_response_callback_);
  DCHECK(fast_pair_data_encryptor);

  // The key based request should only ever be written once; if the notify
  // session has already been set, something has gone wrong.
  DCHECK(!key_based_notify_session_);

  key_based_write_response_callback_ = std::move(write_response_callback);

  const std::array<uint8_t, kBlockSizeBytes> data_to_write =
      fast_pair_data_encryptor->EncryptBytes(CreateRequest(
          message_type, flags, provider_address, seekers_address));
  std::vector<uint8_t> data_to_write_vec(data_to_write.begin(),
                                         data_to_write.end());

  // Append the public version of the private key to the message so the device
  // can generate the shared secret to decrypt the message.
  const std::optional<std::array<uint8_t, 64>> public_key =
      fast_pair_data_encryptor->GetPublicKey();

  if (public_key) {
    const std::vector<uint8_t> public_key_vec = std::vector<uint8_t>(
        public_key.value().begin(), public_key.value().end());
    data_to_write_vec.insert(data_to_write_vec.end(), public_key_vec.begin(),
                             public_key_vec.end());
  }

  keybased_notify_session_start_time_ = base::TimeTicks::Now();
  keybased_notify_session_timer_.Start(
      FROM_HERE, kGattOperationTimeout,
      base::BindOnce(
          &FastPairGattServiceClientImpl::NotifyWriteRequestError,
          weak_ptr_factory_.GetWeakPtr(),
          PairFailure::kKeyBasedPairingCharacteristicNotifySessionTimeout));

  key_based_characteristic_->StartNotifySession(
      base::BindOnce(
          &FastPairGattServiceClientImpl::OnKeyBasedRequestNotifySession,
          weak_ptr_factory_.GetWeakPtr(), data_to_write_vec),
      base::BindOnce(&FastPairGattServiceClientImpl::OnNotifySessionError,
                     weak_ptr_factory_.GetWeakPtr(),
                     PairFailure::kKeyBasedPairingCharacteristicNotifySession));
}

void FastPairGattServiceClientImpl::WritePasskeyAsync(
    uint8_t message_type,
    uint32_t passkey,
    FastPairDataEncryptor* fast_pair_data_encryptor,
    base::OnceCallback<void(std::vector<uint8_t>, std::optional<PairFailure>)>
        write_response_callback) {
  DCHECK(is_initialized_);
  DCHECK(message_type == kSeekerPasskey);

  // |passkey_notify_session| might already exist since it happens after the
  // handshake completes, meaning a reused handshake may already have a
  // |passkey_notify_session|. Therefore, do not DCHECK that it doesn't exist.

  passkey_write_response_callback_ = std::move(write_response_callback);

  const std::array<uint8_t, kBlockSizeBytes> data_to_write =
      fast_pair_data_encryptor->EncryptBytes(
          CreatePasskeyBlock(message_type, passkey));
  std::vector<uint8_t> data_to_write_vec(data_to_write.begin(),
                                         data_to_write.end());

  passkey_notify_session_start_time_ = base::TimeTicks::Now();
  passkey_notify_session_timer_.Start(
      FROM_HERE, kGattOperationTimeout,
      base::BindOnce(&FastPairGattServiceClientImpl::NotifyWritePasskeyError,
                     weak_ptr_factory_.GetWeakPtr(),
                     PairFailure::kPasskeyCharacteristicNotifySessionTimeout));

  passkey_characteristic_->StartNotifySession(
      base::BindOnce(&FastPairGattServiceClientImpl::OnPasskeyNotifySession,
                     weak_ptr_factory_.GetWeakPtr(), data_to_write_vec),
      base::BindOnce(&FastPairGattServiceClientImpl::OnNotifySessionError,
                     weak_ptr_factory_.GetWeakPtr(),
                     PairFailure::kPasskeyCharacteristicNotifySession));
}

void FastPairGattServiceClientImpl::WriteAccountKey(
    std::array<uint8_t, 16> account_key,
    FastPairDataEncryptor* fast_pair_data_encryptor,
    base::OnceCallback<void(std::optional<ash::quick_pair::AccountKeyFailure>)>
        write_account_key_callback) {
  DCHECK(account_key[0] == kAccountKeyStartByte);
  DCHECK(is_initialized_);
  write_account_key_callback_ = std::move(write_account_key_callback);

  const std::array<uint8_t, kBlockSizeBytes> data_to_write =
      fast_pair_data_encryptor->EncryptBytes(account_key);

  WriteGattCharacteristicWithTimeout(
      account_key_characteristic_,
      std::vector<uint8_t>(data_to_write.begin(), data_to_write.end()),
      device::BluetoothRemoteGattCharacteristic::WriteType::kWithResponse,
      base::BindOnce(&FastPairGattServiceClientImpl::NotifyWriteAccountKeyError,
                     weak_ptr_factory_.GetWeakPtr(),
                     ash::quick_pair::AccountKeyFailure::
                         kAccountKeyCharacteristicWriteTimeout),
      base::BindOnce(&FastPairGattServiceClientImpl::OnWriteAccountKey,
                     weak_ptr_factory_.GetWeakPtr(), base::TimeTicks::Now()),
      base::BindOnce(&FastPairGattServiceClientImpl::OnWriteAccountKeyError,
                     weak_ptr_factory_.GetWeakPtr()));
}

void FastPairGattServiceClientImpl::GattCharacteristicValueChanged(
    device::BluetoothAdapter* adapter,
    device::BluetoothRemoteGattCharacteristic* characteristic,
    const std::vector<uint8_t>& value) {
  DCHECK_EQ(adapter, adapter_.get());

  // We check that the callbacks still exists still before we run the
  // it with the response bytes to handle the case where the callback
  // has already been used to notify error. This can happen if the timer for
  // fires with an error, and then the write completes successfully after and
  // we get response bytes here.
  if (characteristic == key_based_characteristic_ &&
      key_based_write_response_callback_) {
    RecordKeyBasedWriteRequestTime(base::TimeTicks::Now() -
                                   key_based_write_request_start_time_);
    StopWriteRequestTimer(key_based_characteristic_);
    std::move(key_based_write_response_callback_)
        .Run(value, /*failure=*/std::nullopt);
    RecordNotifyKeyBasedCharacteristicTime(base::TimeTicks::Now() -
                                           notify_keybased_start_time_);
  } else if (characteristic == passkey_characteristic_ &&
             passkey_write_response_callback_) {
    RecordPasskeyWriteRequestTime(base::TimeTicks::Now() -
                                  passkey_write_request_start_time_);
    StopWriteRequestTimer(passkey_characteristic_);
    RecordNotifyPasskeyCharacteristicTime(base::TimeTicks::Now() -
                                          notify_passkey_start_time_);
    std::move(passkey_write_response_callback_)
        .Run(value, /*failure=*/std::nullopt);
  }
}

void FastPairGattServiceClientImpl::OnWriteRequest() {
  CD_LOG(INFO, Feature::FP) << __func__;
}

void FastPairGattServiceClientImpl::OnWritePasskey() {
  CD_LOG(INFO, Feature::FP) << __func__;
}

void FastPairGattServiceClientImpl::OnWriteRequestError(
    device::BluetoothGattService::GattErrorCode error) {
  CD_LOG(WARNING, Feature::FP) << ": Error: " << ErrorCodeToString(error);
  RecordWriteRequestGattError(error);
  NotifyWriteRequestError(PairFailure::kKeyBasedPairingCharacteristicWrite);
}

void FastPairGattServiceClientImpl::OnWritePasskeyError(
    device::BluetoothGattService::GattErrorCode error) {
  CD_LOG(WARNING, Feature::FP) << ": Error: " << ErrorCodeToString(error);
  RecordWritePasskeyGattError(error);
  NotifyWritePasskeyError(PairFailure::kPasskeyPairingCharacteristicWrite);
}

void FastPairGattServiceClientImpl::OnWriteAccountKey(
    base::TimeTicks write_account_key_start_time) {
  StopWriteRequestTimer(account_key_characteristic_);
  CD_LOG(INFO, Feature::FP) << __func__;
  RecordWriteAccountKeyTime(base::TimeTicks::Now() -
                            write_account_key_start_time);
  std::move(write_account_key_callback_).Run(/*failure=*/std::nullopt);
}

void FastPairGattServiceClientImpl::OnWriteAccountKeyError(
    device::BluetoothGattService::GattErrorCode error) {
  CD_LOG(WARNING, Feature::FP)
      << __func__ << ": Error: " << ErrorCodeToString(error);
  RecordWriteAccountKeyGattError(error);
  NotifyWriteAccountKeyError(GattErrorCodeToAccountKeyFailure(error));
  // |this| may be destroyed after this line.
}

// TODO(b/297104920): ensure this is not called if
// `additional_data_characteristic_` is not discovered.
void FastPairGattServiceClientImpl::WritePersonalizedName(
    const std::string& name,
    const std::string& provider_address,
    FastPairDataEncryptor* fast_pair_data_encryptor,
    base::OnceCallback<void(std::optional<PairFailure>)>
        write_additional_data_callback) {
  DCHECK(write_additional_data_callback_.is_null());
  write_additional_data_callback_ = std::move(write_additional_data_callback);

  // Write Action Request to inform the Additional Data characteristic that the
  // next message is the personalized name.
  const std::array<uint8_t, kBlockSizeBytes> encrypted_data =
      fast_pair_data_encryptor->EncryptBytes(
          CreateActionRequestBeforeAdditionalData(provider_address));

  std::vector<uint8_t> encrypted_request;
  encrypted_request.reserve(kBlockSizeBytes);

  encrypted_request.insert(
      encrypted_request.end(), std::begin(encrypted_data),
      std::next(std::begin(encrypted_data), kBlockSizeBytes));

  WriteGattCharacteristicWithTimeout(
      additional_data_characteristic_, encrypted_request,
      device::BluetoothRemoteGattCharacteristic::WriteType::kWithResponse,
      base::BindOnce(
          &FastPairGattServiceClientImpl::OnWriteAdditionalDataTimeout,
          weak_ptr_factory_.GetWeakPtr()),
      base::BindOnce(
          &FastPairGattServiceClientImpl::OnWritePersonalizedNameRequest,
          weak_ptr_factory_.GetWeakPtr(), name, provider_address,
          fast_pair_data_encryptor),
      base::BindOnce(&FastPairGattServiceClientImpl::OnWriteAdditionalDataError,
                     weak_ptr_factory_.GetWeakPtr()));
}

void FastPairGattServiceClientImpl::WriteGattCharacteristicWithTimeout(
    device::BluetoothRemoteGattCharacteristic* characteristic,
    const std::vector<uint8_t>& encrypted_request,
    device::BluetoothRemoteGattCharacteristic::WriteType write_type,
    base::OnceClosure on_timeout,
    base::OnceClosure on_success,
    base::OnceCallback<void(device::BluetoothGattService::GattErrorCode)>
        on_failure) {
  CHECK(!characteristic_write_request_timers_.contains(characteristic) ||
        !characteristic_write_request_timers_[characteristic]->IsRunning());
  std::unique_ptr<base::OneShotTimer> timer =
      std::make_unique<base::OneShotTimer>();
  timer->Start(FROM_HERE, kGattOperationTimeout, std::move(on_timeout));
  characteristic_write_request_timers_.insert(
      {characteristic, std::move(timer)});
  characteristic->WriteRemoteCharacteristic(
      encrypted_request, write_type,
      base::BindOnce(&FastPairGattServiceClientImpl::StopTimerRunSuccess,
                     weak_ptr_factory_.GetWeakPtr(), characteristic,
                     std::move(on_success)),
      base::BindOnce(&FastPairGattServiceClientImpl::StopTimerRunFailure,
                     weak_ptr_factory_.GetWeakPtr(), characteristic,
                     std::move(on_failure)));
}

void FastPairGattServiceClientImpl::StopTimerRunSuccess(
    device::BluetoothRemoteGattCharacteristic* characteristic,
    base::OnceClosure on_success) {
  StopWriteRequestTimer(characteristic);
  std::move(on_success).Run();
}

void FastPairGattServiceClientImpl::StopTimerRunFailure(
    device::BluetoothRemoteGattCharacteristic* characteristic,
    base::OnceCallback<void(device::BluetoothGattService::GattErrorCode)>
        on_failure,
    device::BluetoothGattService::GattErrorCode error) {
  StopWriteRequestTimer(characteristic);
  std::move(on_failure).Run(/*error=*/error);
}

void FastPairGattServiceClientImpl::StopAllWriteRequestTimers() {
  for (auto& [characteristic, timer] : characteristic_write_request_timers_) {
    if (timer->IsRunning()) {
      timer->Stop();
    }
  }
}

void FastPairGattServiceClientImpl::StopWriteRequestTimer(
    device::BluetoothRemoteGattCharacteristic* characteristic) {
  if (characteristic_write_request_timers_.contains(characteristic)) {
    characteristic_write_request_timers_[characteristic]->Stop();
    characteristic_write_request_timers_.erase(characteristic);
  }
}

void FastPairGattServiceClientImpl::OnWriteAdditionalData() {
  CD_LOG(VERBOSE, Feature::FP) << __func__;
  std::move(write_additional_data_callback_).Run(/*error=*/std::nullopt);
}

void FastPairGattServiceClientImpl::OnWriteAdditionalDataError(
    device::BluetoothGattService::GattErrorCode error) {
  CD_LOG(WARNING, Feature::FP) << ": Error: " << ErrorCodeToString(error);
  std::move(write_additional_data_callback_)
      .Run(
          /*error=*/PairFailure::kAdditionalDataCharacteristicWrite);
}

void FastPairGattServiceClientImpl::OnWriteAdditionalDataTimeout() {
  CD_LOG(WARNING, Feature::FP) << __func__;
  std::move(write_additional_data_callback_)
      .Run(
          /*error=*/PairFailure::kAdditionalDataCharacteristicWriteTimeout);
}

void FastPairGattServiceClientImpl::OnWritePersonalizedNameRequest(
    const std::string& name,
    const std::string& provider_address,
    FastPairDataEncryptor* fast_pair_data_encryptor) {
  CD_LOG(VERBOSE, Feature::FP) << __func__;
  std::array<uint8_t, kNonceSizeBytes> nonce;
  RAND_bytes(nonce.data(), kNonceSizeBytes);
  const std::vector<uint8_t> name_bytes(name.begin(), name.end());

  const std::vector<uint8_t> additional_data_packet =
      fast_pair_data_encryptor->CreateAdditionalDataPacket(nonce, name_bytes);

  WriteGattCharacteristicWithTimeout(
      additional_data_characteristic_, additional_data_packet,
      device::BluetoothRemoteGattCharacteristic::WriteType::kWithResponse,
      base::BindOnce(
          &FastPairGattServiceClientImpl::OnWriteAdditionalDataTimeout,
          weak_ptr_factory_.GetWeakPtr()),
      base::BindOnce(&FastPairGattServiceClientImpl::OnWriteAdditionalData,
                     weak_ptr_factory_.GetWeakPtr()),
      base::BindOnce(&FastPairGattServiceClientImpl::OnWriteAdditionalDataError,
                     weak_ptr_factory_.GetWeakPtr()));
}

}  // namespace quick_pair
}  // namespace ash