| Index: chromeos/components/tether/ble_scanner.cc
|
| diff --git a/chromeos/components/tether/ble_scanner.cc b/chromeos/components/tether/ble_scanner.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..fa86feba6abb4425eb4a5c4bfa3f42e06da8655f
|
| --- /dev/null
|
| +++ b/chromeos/components/tether/ble_scanner.cc
|
| @@ -0,0 +1,312 @@
|
| +// Copyright 2016 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "chromeos/components/tether/ble_scanner.h"
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/memory/ptr_util.h"
|
| +#include "base/strings/string_util.h"
|
| +#include "chromeos/components/tether/ble_constants.h"
|
| +#include "components/cryptauth/proto/cryptauth_api.pb.h"
|
| +#include "components/cryptauth/remote_device.h"
|
| +#include "components/proximity_auth/logging/logging.h"
|
| +#include "device/bluetooth/bluetooth_device.h"
|
| +#include "device/bluetooth/bluetooth_discovery_session.h"
|
| +#include "device/bluetooth/bluetooth_uuid.h"
|
| +
|
| +namespace chromeos {
|
| +
|
| +namespace tether {
|
| +
|
| +namespace {
|
| +
|
| +// Minimum RSSI value to use for discovery. The -90 value was determined
|
| +// empirically and is borrowed from
|
| +// |proximity_auth::BluetoothLowEnergyConnectionFinder|.
|
| +const int kMinDiscoveryRSSI = -90;
|
| +
|
| +// Valid 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 = 4;
|
| +
|
| +// Returns out |string|s data as a hex string.
|
| +std::string StringToHexOfContents(const std::string& string) {
|
| + std::stringstream ss;
|
| + ss << "0x" << std::hex;
|
| +
|
| + for (size_t i = 0; i < string.size(); i++) {
|
| + ss << static_cast<int>(string.data()[i]);
|
| + }
|
| +
|
| + return ss.str();
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +BleScanner::DelegateImpl::DelegateImpl() {}
|
| +
|
| +BleScanner::DelegateImpl::~DelegateImpl() {}
|
| +
|
| +bool BleScanner::DelegateImpl::IsBluetoothAdapterAvailable() const {
|
| + return device::BluetoothAdapterFactory::IsBluetoothAdapterAvailable();
|
| +}
|
| +
|
| +void BleScanner::DelegateImpl::GetAdapter(
|
| + const device::BluetoothAdapterFactory::AdapterCallback& callback) {
|
| + device::BluetoothAdapterFactory::GetAdapter(callback);
|
| +}
|
| +
|
| +const std::vector<uint8_t>* BleScanner::DelegateImpl::GetServiceDataForUUID(
|
| + const device::BluetoothUUID& service_uuid,
|
| + device::BluetoothDevice* bluetooth_device) {
|
| + return bluetooth_device->GetServiceDataForUUID(service_uuid);
|
| +}
|
| +
|
| +BleScanner::BleScanner(
|
| + const LocalDeviceDataProvider* local_device_data_provider)
|
| + : BleScanner(base::MakeUnique<DelegateImpl>(),
|
| + cryptauth::EidGenerator::GetInstance(),
|
| + local_device_data_provider) {}
|
| +
|
| +BleScanner::~BleScanner() {}
|
| +
|
| +BleScanner::BleScanner(
|
| + std::unique_ptr<Delegate> delegate,
|
| + const cryptauth::EidGenerator* eid_generator,
|
| + const LocalDeviceDataProvider* local_device_data_provider)
|
| + : delegate_(std::move(delegate)),
|
| + eid_generator_(eid_generator),
|
| + local_device_data_provider_(local_device_data_provider),
|
| + is_initializing_adapter_(false),
|
| + is_initializing_discovery_session_(false),
|
| + discovery_session_(nullptr),
|
| + weak_ptr_factory_(this) {}
|
| +
|
| +bool BleScanner::RegisterScanFilterForDevice(
|
| + const cryptauth::RemoteDevice& remote_device) {
|
| + if (!delegate_->IsBluetoothAdapterAvailable()) {
|
| + PA_LOG(ERROR) << "Bluetooth is not supported on this platform.";
|
| + return false;
|
| + }
|
| +
|
| + if (registered_remote_devices_.size() >= kMaxConcurrentAdvertisements) {
|
| + // Each scan filter corresponds to an advertisement. Thus, the number of
|
| + // concurrent advertisements cannot exceed the maximum number of concurrent
|
| + // advertisements.
|
| + return false;
|
| + }
|
| +
|
| + std::vector<cryptauth::BeaconSeed> local_device_beacon_seeds;
|
| + if (!local_device_data_provider_->GetLocalDeviceData(
|
| + nullptr, &local_device_beacon_seeds)) {
|
| + // If the local device's beacon seeds could not be fetched, a scan filter
|
| + // cannot be generated.
|
| + return false;
|
| + }
|
| +
|
| + std::unique_ptr<cryptauth::EidGenerator::EidData> scan_filters =
|
| + eid_generator_->GenerateBackgroundScanFilter(local_device_beacon_seeds);
|
| + if (!scan_filters) {
|
| + // If a background scan filter cannot be generated, give up.
|
| + return false;
|
| + }
|
| +
|
| + registered_remote_devices_.push_back(remote_device);
|
| + UpdateDiscoveryStatus();
|
| +
|
| + return true;
|
| +}
|
| +
|
| +bool BleScanner::UnregisterScanFilterForDevice(
|
| + const cryptauth::RemoteDevice& remote_device) {
|
| + for (auto it = registered_remote_devices_.begin();
|
| + it != registered_remote_devices_.end(); ++it) {
|
| + if (it->GetDeviceId() == remote_device.GetDeviceId()) {
|
| + registered_remote_devices_.erase(it);
|
| + UpdateDiscoveryStatus();
|
| + return true;
|
| + }
|
| + }
|
| +
|
| + return false;
|
| +}
|
| +
|
| +bool BleScanner::IsDeviceRegistered(const std::string& device_id) {
|
| + for (auto it = registered_remote_devices_.begin();
|
| + it != registered_remote_devices_.end(); ++it) {
|
| + if (it->GetDeviceId() == device_id) {
|
| + return true;
|
| + }
|
| + }
|
| +
|
| + return false;
|
| +}
|
| +
|
| +void BleScanner::AddObserver(Observer* observer) {
|
| + observer_list_.AddObserver(observer);
|
| +}
|
| +
|
| +void BleScanner::RemoveObserver(Observer* observer) {
|
| + observer_list_.RemoveObserver(observer);
|
| +}
|
| +
|
| +void BleScanner::AdapterPoweredChanged(device::BluetoothAdapter* adapter,
|
| + bool powered) {
|
| + DCHECK_EQ(adapter_.get(), adapter);
|
| + PA_LOG(INFO) << "Adapter power changed. Powered = " << powered;
|
| + UpdateDiscoveryStatus();
|
| +}
|
| +
|
| +void BleScanner::DeviceAdded(device::BluetoothAdapter* adapter,
|
| + device::BluetoothDevice* bluetooth_device) {
|
| + DCHECK_EQ(adapter_.get(), adapter);
|
| + HandleDeviceUpdated(bluetooth_device);
|
| +}
|
| +
|
| +void BleScanner::DeviceChanged(device::BluetoothAdapter* adapter,
|
| + device::BluetoothDevice* bluetooth_device) {
|
| + DCHECK_EQ(adapter_.get(), adapter);
|
| + HandleDeviceUpdated(bluetooth_device);
|
| +}
|
| +
|
| +void BleScanner::UpdateDiscoveryStatus() {
|
| + if (registered_remote_devices_.empty()) {
|
| + StopDiscoverySession();
|
| + return;
|
| + }
|
| +
|
| + if (is_initializing_adapter_) {
|
| + return;
|
| + } else if (!adapter_) {
|
| + InitializeBluetoothAdapter();
|
| + return;
|
| + }
|
| +
|
| + if (!adapter_->IsPowered()) {
|
| + // If the adapter has powered off, no devices can be discovered.
|
| + StopDiscoverySession();
|
| + return;
|
| + }
|
| +
|
| + if (is_initializing_discovery_session_) {
|
| + return;
|
| + } else if (!discovery_session_ ||
|
| + (discovery_session_ && !discovery_session_->IsActive())) {
|
| + StartDiscoverySession();
|
| + }
|
| +}
|
| +
|
| +void BleScanner::InitializeBluetoothAdapter() {
|
| + PA_LOG(INFO) << "Initializing Bluetooth adapter.";
|
| + is_initializing_adapter_ = true;
|
| + delegate_->GetAdapter(base::Bind(&BleScanner::OnAdapterInitialized,
|
| + weak_ptr_factory_.GetWeakPtr()));
|
| +}
|
| +
|
| +void BleScanner::OnAdapterInitialized(
|
| + scoped_refptr<device::BluetoothAdapter> adapter) {
|
| + DCHECK(is_initializing_adapter_ && !discovery_session_ &&
|
| + !is_initializing_discovery_session_);
|
| + PA_LOG(INFO) << "Bluetooth adapter initialized.";
|
| + is_initializing_adapter_ = false;
|
| +
|
| + adapter_ = adapter;
|
| + adapter_->AddObserver(this);
|
| +
|
| + UpdateDiscoveryStatus();
|
| +}
|
| +
|
| +void BleScanner::StartDiscoverySession() {
|
| + DCHECK(adapter_);
|
| + PA_LOG(INFO) << "Starting discovery session.";
|
| + is_initializing_discovery_session_ = true;
|
| +
|
| + // Discover only low energy (LE) devices with strong enough signal.
|
| + std::unique_ptr<device::BluetoothDiscoveryFilter> filter =
|
| + base::MakeUnique<device::BluetoothDiscoveryFilter>(
|
| + device::BLUETOOTH_TRANSPORT_LE);
|
| + filter->SetRSSI(kMinDiscoveryRSSI);
|
| +
|
| + adapter_->StartDiscoverySessionWithFilter(
|
| + std::move(filter), base::Bind(&BleScanner::OnDiscoverySessionStarted,
|
| + weak_ptr_factory_.GetWeakPtr()),
|
| + base::Bind(&BleScanner::OnStartDiscoverySessionError,
|
| + weak_ptr_factory_.GetWeakPtr()));
|
| +}
|
| +
|
| +void BleScanner::OnDiscoverySessionStarted(
|
| + std::unique_ptr<device::BluetoothDiscoverySession> discovery_session) {
|
| + PA_LOG(INFO) << "Discovery session started. Scanning fully initialized.";
|
| + is_initializing_discovery_session_ = false;
|
| + discovery_session_ = std::move(discovery_session);
|
| +}
|
| +
|
| +void BleScanner::OnStartDiscoverySessionError() {
|
| + PA_LOG(WARNING) << "Error starting discovery session. Initialization failed.";
|
| + is_initializing_discovery_session_ = false;
|
| +}
|
| +
|
| +void BleScanner::StopDiscoverySession() {
|
| + if (!discovery_session_) {
|
| + // If there is no discovery session to stop, return early.
|
| + return;
|
| + }
|
| +
|
| + PA_LOG(WARNING) << "Stopping discovery session.";
|
| + discovery_session_.reset();
|
| +}
|
| +
|
| +void BleScanner::HandleDeviceUpdated(
|
| + device::BluetoothDevice* bluetooth_device) {
|
| + DCHECK(bluetooth_device);
|
| +
|
| + const std::vector<uint8_t>* service_data = delegate_->GetServiceDataForUUID(
|
| + device::BluetoothUUID(kAdvertisingServiceUuid), bluetooth_device);
|
| + if (!service_data || service_data->size() < kMinNumBytesInServiceData) {
|
| + // If there is no service data or the service data is of insufficient
|
| + // length, there is not enough information to create a connection.
|
| + return;
|
| + }
|
| +
|
| + // Convert the service data from a std::vector<uint8_t> to a std::string.
|
| + std::string service_data_str;
|
| + char* string_contents_ptr =
|
| + base::WriteInto(&service_data_str, service_data->size() + 1);
|
| + memcpy(string_contents_ptr, service_data->data(), service_data->size() + 1);
|
| +
|
| + CheckForMatchingScanFilters(bluetooth_device, service_data_str);
|
| +}
|
| +
|
| +void BleScanner::CheckForMatchingScanFilters(
|
| + device::BluetoothDevice* bluetooth_device,
|
| + std::string& service_data) {
|
| + std::vector<cryptauth::BeaconSeed> beacon_seeds;
|
| + if (!local_device_data_provider_->GetLocalDeviceData(nullptr,
|
| + &beacon_seeds)) {
|
| + // If no beacon seeds are available, the scan cannot be checked for a match.
|
| + return;
|
| + }
|
| +
|
| + const cryptauth::RemoteDevice* identified_device =
|
| + eid_generator_->IdentifyRemoteDeviceByAdvertisement(
|
| + service_data, registered_remote_devices_, beacon_seeds);
|
| +
|
| + if (identified_device) {
|
| + PA_LOG(INFO) << "Received advertisement from remote device with ID "
|
| + << identified_device->GetTruncatedDeviceIdForLogs() << ".";
|
| + for (auto& observer : observer_list_) {
|
| + observer.OnReceivedAdvertisementFromDevice(bluetooth_device,
|
| + *identified_device);
|
| + }
|
| + } else {
|
| + PA_LOG(INFO) << "Received advertisement remote device, but could not "
|
| + << "identify the device. Service data: "
|
| + << StringToHexOfContents(service_data) << ".";
|
| + }
|
| +}
|
| +
|
| +} // namespace tether
|
| +
|
| +} // namespace chromeos
|
|
|