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

// Copyright 2018 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/bluetooth_helper_impl.h"

#include "ash/constants/ash_features.h"
#include "base/containers/flat_map.h"
#include "base/memory/ptr_util.h"
#include "base/ranges/algorithm.h"
#include "chromeos/ash/components/multidevice/beacon_seed.h"
#include "chromeos/ash/components/multidevice/logging/logging.h"
#include "chromeos/ash/components/multidevice/remote_device_cache.h"
#include "chromeos/ash/components/multidevice/remote_device_ref.h"
#include "chromeos/ash/services/secure_channel/background_eid_generator.h"
#include "chromeos/ash/services/secure_channel/ble_advertisement_generator.h"
#include "chromeos/ash/services/secure_channel/foreground_eid_generator.h"
#include "chromeos/ash/services/secure_channel/public/cpp/shared/ble_constants.h"

namespace ash::secure_channel {

namespace {

// Valid advertisement service data must be at least 2 bytes.
// As of June 2018, valid background advertisement service data is exactly 2
// bytes, which identify the advertising device to the scanning device.
// Valid foreground advertisement service data must include at least 4 bytes:
// 2 bytes associated with the scanning device (used as a scan filter) and 2
// bytes which identify the advertising device to the scanning device.
const size_t kMinNumBytesInServiceData = 2;
const size_t kMaxNumBytesInBackgroundServiceData = 3;
const size_t kMinNumBytesInForegroundServiceData = 4;

}  // namespace

// static
BluetoothHelperImpl::Factory* BluetoothHelperImpl::Factory::test_factory_ =
    nullptr;

// static
std::unique_ptr<BluetoothHelper> BluetoothHelperImpl::Factory::Create(
    multidevice::RemoteDeviceCache* remote_device_cache) {
  if (test_factory_)
    return test_factory_->CreateInstance(remote_device_cache);

  return base::WrapUnique(new BluetoothHelperImpl(remote_device_cache));
}

// static
void BluetoothHelperImpl::Factory::SetFactoryForTesting(Factory* test_factory) {
  test_factory_ = test_factory;
}

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

BluetoothHelperImpl::BluetoothHelperImpl(
    multidevice::RemoteDeviceCache* remote_device_cache)
    : remote_device_cache_(remote_device_cache),
      background_eid_generator_(std::make_unique<BackgroundEidGenerator>()),
      foreground_eid_generator_(std::make_unique<ForegroundEidGenerator>()) {}

BluetoothHelperImpl::~BluetoothHelperImpl() = default;

std::unique_ptr<DataWithTimestamp>
BluetoothHelperImpl::GenerateForegroundAdvertisement(
    const DeviceIdPair& device_id_pair) {
  std::optional<multidevice::RemoteDeviceRef> local_device =
      remote_device_cache_->GetRemoteDevice(
          std::nullopt /* instance_id */,
          device_id_pair.local_device_id() /* legacy_device_id */);
  if (!local_device) {
    PA_LOG(ERROR) << "Requested local device does not exist: "
                  << multidevice::RemoteDeviceRef::TruncateDeviceIdForLogs(
                         device_id_pair.local_device_id());
    return nullptr;
  }

  std::optional<multidevice::RemoteDeviceRef> remote_device =
      remote_device_cache_->GetRemoteDevice(
          std::nullopt /* instance_id */,
          device_id_pair.remote_device_id() /* legacy_device_id */);
  if (!remote_device) {
    PA_LOG(ERROR) << "Requested remote device does not exist: "
                  << multidevice::RemoteDeviceRef::TruncateDeviceIdForLogs(
                         device_id_pair.remote_device_id());
    return nullptr;
  }

  return BleAdvertisementGenerator::GenerateBleAdvertisement(
      *remote_device, local_device->public_key());
}

std::optional<BluetoothHelper::DeviceWithBackgroundBool>
BluetoothHelperImpl::PerformIdentifyRemoteDevice(
    const std::string& service_data,
    const DeviceIdPairSet& device_id_pair_set) {
  base::flat_map<std::string, std::vector<std::string>>
      local_device_id_to_remote_device_ids_map;
  for (const auto& device_id_pair : device_id_pair_set) {
    if (!remote_device_cache_->GetRemoteDevice(
            std::nullopt /* instance_id */,
            device_id_pair.local_device_id() /* legacy_device_id */)) {
      PA_LOG(ERROR) << "Requested local device does not exist"
                    << multidevice::RemoteDeviceRef::TruncateDeviceIdForLogs(
                           device_id_pair.local_device_id());
      continue;
    }

    if (!remote_device_cache_->GetRemoteDevice(
            std::nullopt /* instance_id */,
            device_id_pair.remote_device_id() /* legacy_device_id */)) {
      PA_LOG(ERROR) << "Requested remote device does not exist"
                    << multidevice::RemoteDeviceRef::TruncateDeviceIdForLogs(
                           device_id_pair.remote_device_id());
      continue;
    }

    local_device_id_to_remote_device_ids_map[device_id_pair.local_device_id()]
        .push_back(device_id_pair.remote_device_id());
  }

  for (const auto& map_entry : local_device_id_to_remote_device_ids_map) {
    auto device_with_background_bool = PerformIdentifyRemoteDevice(
        service_data, map_entry.first, map_entry.second);
    if (device_with_background_bool)
      return device_with_background_bool;
  }

  return std::nullopt;
}

std::string BluetoothHelperImpl::GetBluetoothPublicAddress(
    const std::string& device_id) {
  std::optional<multidevice::RemoteDeviceRef> device =
      remote_device_cache_->GetRemoteDevice(std::nullopt /* instance_id */,
                                            device_id /* legacy_device_id */);
  if (device)
    return device->bluetooth_public_address();
  return std::string();
}

std::string BluetoothHelperImpl::ExpectedServiceDataToString(
    const DeviceIdPairSet& device_id_pair_set) {
  std::stringstream ss;

  for (const DeviceIdPair& pair : device_id_pair_set) {
    ss << "{Device ID: "
       << multidevice::RemoteDeviceRef::TruncateDeviceIdForLogs(
              pair.remote_device_id())
       << " - ";

    std::optional<multidevice::RemoteDeviceRef> device =
        remote_device_cache_->GetRemoteDevice(
            std::nullopt /* instance_id */,
            pair.remote_device_id() /* legacy_device_id */);

    if (!device) {
      ss << "<missing metadata>},";
      continue;
    }

    std::vector<DataWithTimestamp> data_for_device =
        background_eid_generator_->GenerateNearestEids(
            multidevice::ToCryptAuthSeedList(device->beacon_seeds()));
    if (data_for_device.empty()) {
      ss << "[]},";
      continue;
    }

    ss << DataWithTimestamp::ToDebugString(data_for_device) << "},";
  }

  return ss.str();
}

std::optional<BluetoothHelper::DeviceWithBackgroundBool>
BluetoothHelperImpl::PerformIdentifyRemoteDevice(
    const std::string& service_data,
    const std::string& local_device_id,
    const std::vector<std::string>& remote_device_ids) {
  std::string identified_device_id;
  bool is_background_advertisement = false;

  // First try, identifying |service_data| as a foreground advertisement.
  if (service_data.size() >= kMinNumBytesInForegroundServiceData) {
    std::vector<cryptauth::BeaconSeed> beacon_seeds =
        multidevice::ToCryptAuthSeedList(
            remote_device_cache_
                ->GetRemoteDevice(std::nullopt /* instance_id */,
                                  local_device_id /* legacy_device_id */)
                ->beacon_seeds());

    identified_device_id =
        foreground_eid_generator_->IdentifyRemoteDeviceByAdvertisement(
            service_data, remote_device_ids, beacon_seeds);
  }

  // If the device has not yet been identified, try identifying |service_data|
  // as a background advertisement.
  if (features::IsInstantTetheringBackgroundAdvertisingSupported() &&
      identified_device_id.empty() &&
      service_data.size() >= kMinNumBytesInServiceData &&
      service_data.size() <= kMaxNumBytesInBackgroundServiceData) {
    multidevice::RemoteDeviceRefList remote_devices;
    base::ranges::transform(
        remote_device_ids, std::back_inserter(remote_devices),
        [this](auto device_id) {
          return *remote_device_cache_->GetRemoteDevice(
              std::nullopt /* instance_id */, device_id /* legacy_device_id */);
        });

    identified_device_id =
        background_eid_generator_->IdentifyRemoteDeviceByAdvertisement(
            service_data, remote_devices);
    is_background_advertisement = true;
  }

  // If the service data does not correspond to an advertisement from a device
  // on this account, ignore it.
  if (identified_device_id.empty())
    return std::nullopt;

  return BluetoothHelper::DeviceWithBackgroundBool(
      *remote_device_cache_->GetRemoteDevice(
          std::nullopt /* instance_id */,
          identified_device_id /* legacy_device_id */),
      is_background_advertisement);
}

void BluetoothHelperImpl::SetTestDoubles(
    std::unique_ptr<BackgroundEidGenerator> background_eid_generator,
    std::unique_ptr<ForegroundEidGenerator> foreground_eid_generator) {
  background_eid_generator_ = std::move(background_eid_generator);
  foreground_eid_generator_ = std::move(foreground_eid_generator);
}

}  // namespace ash::secure_channel