chromium/chromeos/ash/services/secure_channel/ble_characteristics_finder.cc

// Copyright 2015 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/secure_channel/ble_characteristics_finder.h"

#include "base/functional/bind.h"
#include "base/strings/string_util.h"
#include "chromeos/ash/components/multidevice/logging/logging.h"
#include "chromeos/ash/services/secure_channel/background_eid_generator.h"
#include "device/bluetooth/bluetooth_adapter.h"
#include "device/bluetooth/bluetooth_device.h"
#include "device/bluetooth/bluetooth_remote_gatt_characteristic.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"

namespace ash::secure_channel {

using ::device::BluetoothAdapter;
using ::device::BluetoothDevice;
using ::device::BluetoothRemoteGattCharacteristic;
using ::device::BluetoothRemoteGattService;
using ::device::BluetoothUUID;

namespace {

// The UUID of the characteristic for eid verification.
const char kEidCharacteristicUuid[] = "f21843b0-9411-434b-b85f-a9b92bd69f77";

}  // namespace

BluetoothLowEnergyCharacteristicsFinder::
    BluetoothLowEnergyCharacteristicsFinder(
        scoped_refptr<BluetoothAdapter> adapter,
        BluetoothDevice* device,
        const RemoteAttribute& remote_service,
        const RemoteAttribute& to_peripheral_char,
        const RemoteAttribute& from_peripheral_char,
        SuccessCallback success_callback,
        base::OnceClosure error_callback,
        const multidevice::RemoteDeviceRef& remote_device,
        std::unique_ptr<BackgroundEidGenerator> background_eid_generator,
        scoped_refptr<base::TaskRunner> task_runner)
    : adapter_(adapter),
      bluetooth_device_(device),
      remote_service_(remote_service),
      to_peripheral_char_(to_peripheral_char),
      from_peripheral_char_(from_peripheral_char),
      success_callback_(std::move(success_callback)),
      error_callback_(std::move(error_callback)),
      remote_device_(remote_device),
      background_eid_generator_(std::move(background_eid_generator)) {
  task_runner->PostTask(
      FROM_HERE, base::BindOnce(&BluetoothLowEnergyCharacteristicsFinder::Start,
                                weak_ptr_factory_.GetWeakPtr()));
}

BluetoothLowEnergyCharacteristicsFinder::
    BluetoothLowEnergyCharacteristicsFinder(
        const multidevice::RemoteDeviceRef& remote_device)
    : remote_device_(remote_device) {}

BluetoothLowEnergyCharacteristicsFinder::
    ~BluetoothLowEnergyCharacteristicsFinder() {
  if (adapter_) {
    adapter_->RemoveObserver(this);
  }
}

void BluetoothLowEnergyCharacteristicsFinder::Start() {
  adapter_->AddObserver(this);
  if (bluetooth_device_->IsGattServicesDiscoveryComplete())
    ScanRemoteCharacteristics();
}

void BluetoothLowEnergyCharacteristicsFinder::GattServicesDiscovered(
    BluetoothAdapter* adapter,
    BluetoothDevice* device) {
  // Ignore events about other devices.
  if (device != bluetooth_device_)
    return;
  PA_LOG(VERBOSE) << "All services discovered.";

  ScanRemoteCharacteristics();
}

void BluetoothLowEnergyCharacteristicsFinder::ScanRemoteCharacteristics() {
  if (have_services_been_parsed_)
    return;

  have_services_been_parsed_ = true;
  base::flat_set<BluetoothRemoteGattCharacteristic*>
      eid_characteristics_to_check;
  for (const BluetoothRemoteGattService* service :
       bluetooth_device_->GetGattServices()) {
    if (service->GetUUID() != remote_service_.uuid)
      continue;

    std::vector<BluetoothRemoteGattCharacteristic*> tx_chars =
        service->GetCharacteristicsByUUID(to_peripheral_char_.uuid);
    std::vector<BluetoothRemoteGattCharacteristic*> rx_chars =
        service->GetCharacteristicsByUUID(from_peripheral_char_.uuid);
    std::vector<BluetoothRemoteGattCharacteristic*> eid_chars =
        service->GetCharacteristicsByUUID(
            device::BluetoothUUID(kEidCharacteristicUuid));

    if (tx_chars.empty()) {
      PA_LOG(WARNING) << "Service missing TX char.";
      continue;
    }
    if (rx_chars.empty()) {
      PA_LOG(WARNING) << "Service missing RX char.";
      continue;
    }

    // If the GATT service has a TX and RX characteristic, but no EID
    // characteristic, the phone does not require an EID check. Either this is
    // a phone running an old version of GmsCore, or it does not have a work
    // profile. This is the right GATT service to use already.
    if (eid_chars.empty()) {
      NotifySuccess(service->GetIdentifier(), tx_chars.front()->GetIdentifier(),
                    rx_chars.front()->GetIdentifier());
      return;
    }

    eid_characteristics_to_check.insert(eid_chars.front());
    service_ids_pending_eid_read_.insert(service->GetIdentifier());
  }

  // If there were eid characteristics found when parsing services, read their
  // values and compare them to expected device EIDs to determine whether each
  // is the right GATT service.
  if (!eid_characteristics_to_check.empty()) {
    for (BluetoothRemoteGattCharacteristic* eid_char :
         eid_characteristics_to_check)
      TryToVerifyEid(eid_char);
    return;
  }

  // If all GATT services have been discovered and we haven't found the
  // characteristics we are looking for, call the error callback.
  NotifyFailureIfNoPendingEidCharReads();
}

void BluetoothLowEnergyCharacteristicsFinder::NotifySuccess(
    std::string service_id,
    std::string tx_id,
    std::string rx_id) {
  DCHECK(!has_callback_been_invoked_);
  has_callback_been_invoked_ = true;
  from_peripheral_char_.id = rx_id;
  to_peripheral_char_.id = tx_id;
  remote_service_.id = service_id;
  std::move(success_callback_)
      .Run(remote_service_, to_peripheral_char_, from_peripheral_char_);
}

void BluetoothLowEnergyCharacteristicsFinder::
    NotifyFailureIfNoPendingEidCharReads() {
  if (!service_ids_pending_eid_read_.empty())
    return;
  DCHECK(!has_callback_been_invoked_);
  has_callback_been_invoked_ = true;
  std::move(error_callback_).Run();
}

void BluetoothLowEnergyCharacteristicsFinder::TryToVerifyEid(
    device::BluetoothRemoteGattCharacteristic* eid_char) {
  eid_char->ReadRemoteCharacteristic(base::BindOnce(
      &BluetoothLowEnergyCharacteristicsFinder::OnRemoteCharacteristicRead,
      weak_ptr_factory_.GetWeakPtr(), eid_char->GetService()->GetIdentifier()));
}

void BluetoothLowEnergyCharacteristicsFinder::OnRemoteCharacteristicRead(
    const std::string& service_id,
    std::optional<device::BluetoothGattService::GattErrorCode> error_code,
    const std::vector<uint8_t>& value) {
  auto it = service_ids_pending_eid_read_.find(service_id);
  if (it == service_ids_pending_eid_read_.end()) {
    PA_LOG(WARNING) << "No request entry for " << service_id;
    return;
  }

  service_ids_pending_eid_read_.erase(it);

  if (error_code.has_value()) {
    PA_LOG(ERROR) << "OnWriteRemoteCharacteristicError() Error code: "
                  << static_cast<int>(error_code.value());
    service_ids_pending_eid_read_.erase(service_id);
    if (!has_callback_been_invoked_)
      NotifyFailureIfNoPendingEidCharReads();
    return;
  }

  if (has_callback_been_invoked_) {
    PA_LOG(VERBOSE) << "Characteristic read after callback was invoked.";
    return;
  }

  if (!DoesEidMatchExpectedDevice(value)) {
    if (!has_callback_been_invoked_)
      NotifyFailureIfNoPendingEidCharReads();
    return;
  }

  // Found the right GATT service! Grab identifiers and trigger success.
  const BluetoothRemoteGattService* service =
      bluetooth_device_->GetGattService(service_id);

  if (!service) {
    if (!has_callback_been_invoked_)
      NotifyFailureIfNoPendingEidCharReads();
    return;
  }

  std::vector<BluetoothRemoteGattCharacteristic*> tx_chars =
      service->GetCharacteristicsByUUID(to_peripheral_char_.uuid);
  std::vector<BluetoothRemoteGattCharacteristic*> rx_chars =
      service->GetCharacteristicsByUUID(from_peripheral_char_.uuid);
  NotifySuccess(service_id, tx_chars.front()->GetIdentifier(),
                rx_chars.front()->GetIdentifier());
}

bool BluetoothLowEnergyCharacteristicsFinder::DoesEidMatchExpectedDevice(
    const std::vector<uint8_t>& eid_value_read) {
  std::string eid_char_data_str(eid_value_read.begin(), eid_value_read.end());

  multidevice::RemoteDeviceRefList remote_device_list{remote_device_};
  std::string identified_device_id =
      background_eid_generator_->IdentifyRemoteDeviceByAdvertisement(
          eid_char_data_str, remote_device_list);
  return !identified_device_id.empty();
}

}  // namespace ash::secure_channel