| // Copyright 2017 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_impl.h" |
| |
| #include <memory> |
| |
| #include "base/bind.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/string_util.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "chromeos/chromeos_switches.h" |
| #include "chromeos/components/proximity_auth/logging/logging.h" |
| #include "chromeos/components/tether/tether_host_fetcher.h" |
| #include "chromeos/services/secure_channel/ble_constants.h" |
| #include "chromeos/services/secure_channel/ble_service_data_helper.h" |
| #include "chromeos/services/secure_channel/ble_synchronizer.h" |
| #include "chromeos/services/secure_channel/device_id_pair.h" |
| #include "components/cryptauth/proto/cryptauth_api.pb.h" |
| #include "components/cryptauth/remote_device_ref.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 { |
| |
| // Instant Tethering does not make use of the "local device ID" argument, since |
| // all connections are from the same device. |
| // TODO(hansberry): Remove when SecureChannelClient migration is complete. |
| const char kStubLocalDeviceId[] = "N/A"; |
| |
| // Valid advertisement service data must be at least 2 bytes. |
| // As of March 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; |
| |
| } // namespace |
| |
| // static |
| BleScannerImpl::Factory* BleScannerImpl::Factory::factory_instance_ = nullptr; |
| |
| // static |
| std::unique_ptr<BleScanner> BleScannerImpl::Factory::NewInstance( |
| scoped_refptr<device::BluetoothAdapter> adapter, |
| secure_channel::BleServiceDataHelper* ble_service_data_helper, |
| secure_channel::BleSynchronizerBase* ble_synchronizer, |
| TetherHostFetcher* tether_host_fetcher) { |
| if (!factory_instance_) |
| factory_instance_ = new Factory(); |
| |
| return factory_instance_->BuildInstance( |
| adapter, ble_service_data_helper, ble_synchronizer, tether_host_fetcher); |
| } |
| |
| // static |
| void BleScannerImpl::Factory::SetInstanceForTesting(Factory* factory) { |
| factory_instance_ = factory; |
| } |
| |
| std::unique_ptr<BleScanner> BleScannerImpl::Factory::BuildInstance( |
| scoped_refptr<device::BluetoothAdapter> adapter, |
| secure_channel::BleServiceDataHelper* ble_service_data_helper, |
| secure_channel::BleSynchronizerBase* ble_synchronizer, |
| TetherHostFetcher* tether_host_fetcher) { |
| return base::WrapUnique(new BleScannerImpl( |
| adapter, ble_service_data_helper, ble_synchronizer, tether_host_fetcher)); |
| } |
| |
| BleScannerImpl::ServiceDataProviderImpl::ServiceDataProviderImpl() = default; |
| |
| BleScannerImpl::ServiceDataProviderImpl::~ServiceDataProviderImpl() = default; |
| |
| const std::vector<uint8_t>* |
| BleScannerImpl::ServiceDataProviderImpl::GetServiceDataForUUID( |
| device::BluetoothDevice* bluetooth_device) { |
| return bluetooth_device->GetServiceDataForUUID( |
| device::BluetoothUUID(secure_channel::kAdvertisingServiceUuid)); |
| } |
| |
| BleScannerImpl::BleScannerImpl( |
| scoped_refptr<device::BluetoothAdapter> adapter, |
| secure_channel::BleServiceDataHelper* ble_service_data_helper, |
| secure_channel::BleSynchronizerBase* ble_synchronizer, |
| TetherHostFetcher* tether_host_fetcher) |
| : adapter_(adapter), |
| ble_service_data_helper_(ble_service_data_helper), |
| ble_synchronizer_(ble_synchronizer), |
| tether_host_fetcher_(tether_host_fetcher), |
| service_data_provider_(std::make_unique<ServiceDataProviderImpl>()), |
| task_runner_(base::ThreadTaskRunnerHandle::Get()), |
| weak_ptr_factory_(this) { |
| adapter_->AddObserver(this); |
| } |
| |
| BleScannerImpl::~BleScannerImpl() { |
| adapter_->RemoveObserver(this); |
| } |
| |
| bool BleScannerImpl::RegisterScanFilterForDevice(const std::string& device_id) { |
| if (registered_remote_device_ids_.size() >= |
| secure_channel::kMaxConcurrentAdvertisements) { |
| // Each scan filter corresponds to an advertisement. Thus, the number of |
| // concurrent advertisements cannot exceed the maximum number of concurrent |
| // advertisements. |
| PA_LOG(WARNING) << "Attempted to start a scan for a new device when the " |
| << "maximum number of devices have already been " |
| << "registered."; |
| return false; |
| } |
| |
| registered_remote_device_ids_.push_back(device_id); |
| UpdateDiscoveryStatus(); |
| |
| return true; |
| } |
| |
| bool BleScannerImpl::UnregisterScanFilterForDevice( |
| const std::string& device_id) { |
| for (auto it = registered_remote_device_ids_.begin(); |
| it != registered_remote_device_ids_.end(); ++it) { |
| if (*it == device_id) { |
| registered_remote_device_ids_.erase(it); |
| UpdateDiscoveryStatus(); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool BleScannerImpl::ShouldDiscoverySessionBeActive() { |
| return !registered_remote_device_ids_.empty(); |
| } |
| |
| bool BleScannerImpl::IsDiscoverySessionActive() { |
| ResetDiscoverySessionIfNotActive(); |
| |
| if (discovery_session_) { |
| // Once the session is stopped, the pointer is cleared. |
| DCHECK(discovery_session_->IsActive()); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void BleScannerImpl::SetTestDoubles( |
| std::unique_ptr<ServiceDataProvider> service_data_provider, |
| scoped_refptr<base::TaskRunner> test_task_runner) { |
| service_data_provider_ = std::move(service_data_provider); |
| task_runner_ = test_task_runner; |
| } |
| |
| bool BleScannerImpl::IsDeviceRegistered(const std::string& device_id) { |
| return base::ContainsValue(registered_remote_device_ids_, device_id); |
| } |
| |
| void BleScannerImpl::DeviceAdded(device::BluetoothAdapter* adapter, |
| device::BluetoothDevice* bluetooth_device) { |
| DCHECK_EQ(adapter_.get(), adapter); |
| HandleDeviceUpdated(bluetooth_device); |
| } |
| |
| void BleScannerImpl::DeviceChanged(device::BluetoothAdapter* adapter, |
| device::BluetoothDevice* bluetooth_device) { |
| DCHECK_EQ(adapter_.get(), adapter); |
| HandleDeviceUpdated(bluetooth_device); |
| } |
| |
| void BleScannerImpl::ResetDiscoverySessionIfNotActive() { |
| if (!discovery_session_ || discovery_session_->IsActive()) |
| return; |
| |
| PA_LOG(ERROR) << "BluetoothDiscoverySession became out of sync. Session is " |
| << "no longer active, but it was never stopped successfully. " |
| << "Resetting session."; |
| |
| // |discovery_session_| should be deleted whenever the session is no longer |
| // active. However, due to Bluetooth bugs, this does not always occur |
| // properly. When we detect that this situation has occurred, delete the |
| // pointer and reset discovery state. |
| discovery_session_.reset(); |
| discovery_session_weak_ptr_factory_.reset(); |
| is_initializing_discovery_session_ = false; |
| is_stopping_discovery_session_ = false; |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| |
| ScheduleStatusChangeNotification(false /* discovery_session_active */); |
| } |
| |
| void BleScannerImpl::UpdateDiscoveryStatus() { |
| if (ShouldDiscoverySessionBeActive()) |
| EnsureDiscoverySessionActive(); |
| else |
| EnsureDiscoverySessionNotActive(); |
| } |
| |
| void BleScannerImpl::EnsureDiscoverySessionActive() { |
| // If the session is active or is in the process of becoming active, there is |
| // nothing to do. |
| if (IsDiscoverySessionActive() || is_initializing_discovery_session_) |
| return; |
| |
| is_initializing_discovery_session_ = true; |
| |
| ble_synchronizer_->StartDiscoverySession( |
| base::Bind(&BleScannerImpl::OnDiscoverySessionStarted, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::Bind(&BleScannerImpl::OnStartDiscoverySessionError, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void BleScannerImpl::OnDiscoverySessionStarted( |
| std::unique_ptr<device::BluetoothDiscoverySession> discovery_session) { |
| is_initializing_discovery_session_ = false; |
| PA_LOG(INFO) << "Started discovery session successfully."; |
| |
| discovery_session_ = std::move(discovery_session); |
| discovery_session_weak_ptr_factory_ = |
| std::make_unique<base::WeakPtrFactory<device::BluetoothDiscoverySession>>( |
| discovery_session_.get()); |
| |
| ScheduleStatusChangeNotification(true /* discovery_session_active */); |
| |
| UpdateDiscoveryStatus(); |
| } |
| |
| void BleScannerImpl::OnStartDiscoverySessionError() { |
| PA_LOG(ERROR) << "Error starting discovery session. Initialization failed."; |
| is_initializing_discovery_session_ = false; |
| UpdateDiscoveryStatus(); |
| } |
| |
| void BleScannerImpl::EnsureDiscoverySessionNotActive() { |
| // If there is no session, there is nothing to do. |
| if (!IsDiscoverySessionActive() || is_stopping_discovery_session_) |
| return; |
| |
| is_stopping_discovery_session_ = true; |
| |
| ble_synchronizer_->StopDiscoverySession( |
| discovery_session_weak_ptr_factory_->GetWeakPtr(), |
| base::Bind(&BleScannerImpl::OnDiscoverySessionStopped, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::Bind(&BleScannerImpl::OnStopDiscoverySessionError, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void BleScannerImpl::OnDiscoverySessionStopped() { |
| is_stopping_discovery_session_ = false; |
| PA_LOG(INFO) << "Stopped discovery session successfully."; |
| |
| discovery_session_.reset(); |
| discovery_session_weak_ptr_factory_.reset(); |
| |
| ScheduleStatusChangeNotification(false /* discovery_session_active */); |
| |
| UpdateDiscoveryStatus(); |
| } |
| |
| void BleScannerImpl::OnStopDiscoverySessionError() { |
| PA_LOG(ERROR) << "Error stopping discovery session."; |
| is_stopping_discovery_session_ = false; |
| UpdateDiscoveryStatus(); |
| } |
| |
| void BleScannerImpl::HandleDeviceUpdated( |
| device::BluetoothDevice* bluetooth_device) { |
| DCHECK(bluetooth_device); |
| |
| const std::vector<uint8_t>* service_data = |
| service_data_provider_->GetServiceDataForUUID(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()); |
| |
| CheckForMatchingScanFilters(bluetooth_device, service_data_str); |
| } |
| |
| void BleScannerImpl::CheckForMatchingScanFilters( |
| device::BluetoothDevice* bluetooth_device, |
| const std::string& service_data) { |
| secure_channel::DeviceIdPairSet device_id_pair_set; |
| for (const auto& remote_device_id : registered_remote_device_ids_) |
| device_id_pair_set.emplace(remote_device_id, kStubLocalDeviceId); |
| |
| base::Optional<secure_channel::BleServiceDataHelper::DeviceWithBackgroundBool> |
| device_with_background_bool = |
| ble_service_data_helper_->IdentifyRemoteDevice(service_data, |
| device_id_pair_set); |
| |
| // If the service data does not correspond to an advertisement from a device |
| // on this account, ignore it. |
| if (!device_with_background_bool) |
| return; |
| |
| NotifyReceivedAdvertisementFromDevice( |
| device_with_background_bool->first /* remote_device */, bluetooth_device, |
| device_with_background_bool->second /* is_background_advertisement */); |
| } |
| |
| void BleScannerImpl::ScheduleStatusChangeNotification( |
| bool discovery_session_active) { |
| // Schedule the task to run after the current task has completed. This is |
| // necessary because the completion of a Bluetooth task may cause the Tether |
| // component to be shut down; if that occurs, then we cannot reference |
| // instance variables in this class after the object has been deleted. |
| // Completing the current command as part of the next task ensures that this |
| // cannot occur. See crbug.com/776241. |
| task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&BleScannerImpl::NotifyDiscoverySessionStateChanged, |
| weak_ptr_factory_.GetWeakPtr(), discovery_session_active)); |
| } |
| |
| } // namespace tether |
| |
| } // namespace chromeos |