diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-10-24 11:30:15 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-10-30 12:56:19 +0000 |
commit | 6036726eb981b6c4b42047513b9d3f4ac865daac (patch) | |
tree | 673593e70678e7789766d1f732eb51f613a2703b /chromium/device | |
parent | 466052c4e7c052268fd931888cd58961da94c586 (diff) | |
download | qtwebengine-chromium-6036726eb981b6c4b42047513b9d3f4ac865daac.tar.gz |
BASELINE: Update Chromium to 70.0.3538.78
Change-Id: Ie634710bf039e26c1957f4ae45e101bd4c434ae7
Reviewed-by: Michael Brüning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/device')
368 files changed, 13199 insertions, 4906 deletions
diff --git a/chromium/device/BUILD.gn b/chromium/device/BUILD.gn index 17eb38aef83..46218c909fc 100644 --- a/chromium/device/BUILD.gn +++ b/chromium/device/BUILD.gn @@ -62,25 +62,30 @@ test("device_unittests") { "bluetooth/test/mock_bluetooth_central_manager_mac.mm", "bluetooth/test/test_bluetooth_adapter_observer.cc", "bluetooth/test/test_bluetooth_adapter_observer.h", + "bluetooth/test/test_bluetooth_advertisement_observer.cc", + "bluetooth/test/test_bluetooth_advertisement_observer.h", "bluetooth/test/test_bluetooth_local_gatt_service_delegate.cc", "bluetooth/test/test_bluetooth_local_gatt_service_delegate.h", + "bluetooth/test/test_pairing_delegate.cc", + "bluetooth/test/test_pairing_delegate.h", "bluetooth/uribeacon/uri_encoder_unittest.cc", "fido/attestation_statement_formats_unittest.cc", + "fido/ble/fido_ble_connection_unittest.cc", + "fido/ble/fido_ble_device_unittest.cc", + "fido/ble/fido_ble_frames_unittest.cc", + "fido/ble_adapter_power_manager_unittest.cc", + "fido/cable/fido_cable_device_unittest.cc", + "fido/cable/fido_cable_discovery_unittest.cc", + "fido/cable/fido_cable_handshake_handler_unittest.cc", "fido/ctap_request_unittest.cc", "fido/ctap_response_unittest.cc", "fido/fake_fido_discovery_unittest.cc", - "fido/fido_ble_connection_unittest.cc", - "fido/fido_ble_device_unittest.cc", - "fido/fido_ble_frames_unittest.cc", - "fido/fido_cable_device_unittest.cc", - "fido/fido_cable_discovery_unittest.cc", - "fido/fido_cable_handshake_handler_unittest.cc", "fido/fido_discovery_unittest.cc", - "fido/fido_hid_message_unittest.cc", "fido/fido_parsing_utils_unittest.cc", "fido/fido_request_handler_unittest.cc", "fido/get_assertion_handler_unittest.cc", "fido/get_assertion_task_unittest.cc", + "fido/hid/fido_hid_message_unittest.cc", "fido/mac/browsing_data_deletion_unittest.mm", "fido/mac/credential_metadata_unittest.cc", "fido/mac/get_assertion_operation_unittest_mac.mm", @@ -95,7 +100,7 @@ test("device_unittests") { "gamepad/abstract_haptic_gamepad_unittest.cc", "gamepad/gamepad_provider_unittest.cc", "gamepad/gamepad_service_unittest.cc", - "gamepad/public/mojom/gamepad_mojom_traits_unittest.cc", + "gamepad/public/cpp/gamepad_mojom_traits_unittest.cc", "test/run_all_unittests.cc", ] @@ -116,9 +121,9 @@ test("device_unittests") { "//device/gamepad:test_helpers", "//device/gamepad/public/cpp:shared_with_blink", "//device/gamepad/public/mojom", - "//device/gamepad/public/mojom:gamepad_mojom_traits_test", "//mojo/core/embedder", "//mojo/public/cpp/bindings", + "//mojo/public/cpp/test_support:test_utils", "//net", "//testing/gmock", "//testing/gtest", @@ -132,8 +137,8 @@ test("device_unittests") { # Linux, requires udev. if (!is_linux_without_udev && !is_android) { sources += [ - "fido/fido_hid_device_unittest.cc", - "fido/fido_hid_discovery_unittest.cc", + "fido/hid/fido_hid_device_unittest.cc", + "fido/hid/fido_hid_discovery_unittest.cc", ] deps += [ "//device/fido:test_support", @@ -165,8 +170,6 @@ test("device_unittests") { "test/test_device_client.h", "usb/mojo/device_impl_unittest.cc", "usb/mojo/device_manager_impl_unittest.cc", - "usb/mojo/mock_permission_provider.cc", - "usb/mojo/mock_permission_provider.h", "usb/public/cpp/filter_utils_unittest.cc", "usb/usb_descriptors_unittest.cc", "usb/usb_device_handle_unittest.cc", @@ -255,7 +258,7 @@ test("device_unittests") { ] } else { # BLE discovery: works on Linux. - sources += [ "fido/fido_ble_discovery_unittest.cc" ] + sources += [ "fido/ble/fido_ble_discovery_unittest.cc" ] } } @@ -287,6 +290,12 @@ test("device_unittests") { "bluetooth/bluetooth_low_energy_win_fake.h", "bluetooth/test/fake_bluetooth_adapter_winrt.cc", "bluetooth/test/fake_bluetooth_adapter_winrt.h", + "bluetooth/test/fake_bluetooth_le_advertisement_data_section_winrt.cc", + "bluetooth/test/fake_bluetooth_le_advertisement_data_section_winrt.h", + "bluetooth/test/fake_bluetooth_le_advertisement_publisher_status_changed_event_args_winrt.cc", + "bluetooth/test/fake_bluetooth_le_advertisement_publisher_status_changed_event_args_winrt.h", + "bluetooth/test/fake_bluetooth_le_advertisement_publisher_winrt.cc", + "bluetooth/test/fake_bluetooth_le_advertisement_publisher_winrt.h", "bluetooth/test/fake_bluetooth_le_advertisement_received_event_args_winrt.cc", "bluetooth/test/fake_bluetooth_le_advertisement_received_event_args_winrt.h", "bluetooth/test/fake_bluetooth_le_advertisement_watcher_winrt.cc", @@ -295,16 +304,38 @@ test("device_unittests") { "bluetooth/test/fake_bluetooth_le_advertisement_winrt.h", "bluetooth/test/fake_bluetooth_le_device_winrt.cc", "bluetooth/test/fake_bluetooth_le_device_winrt.h", + "bluetooth/test/fake_bluetooth_le_manufacturer_data_winrt.cc", + "bluetooth/test/fake_bluetooth_le_manufacturer_data_winrt.h", + "bluetooth/test/fake_device_information_custom_pairing_winrt.cc", + "bluetooth/test/fake_device_information_custom_pairing_winrt.h", + "bluetooth/test/fake_device_information_pairing_winrt.cc", + "bluetooth/test/fake_device_information_pairing_winrt.h", "bluetooth/test/fake_device_information_winrt.cc", "bluetooth/test/fake_device_information_winrt.h", + "bluetooth/test/fake_device_pairing_requested_event_args_winrt.cc", + "bluetooth/test/fake_device_pairing_requested_event_args_winrt.h", + "bluetooth/test/fake_device_pairing_result_winrt.cc", + "bluetooth/test/fake_device_pairing_result_winrt.h", + "bluetooth/test/fake_device_watcher_winrt.cc", + "bluetooth/test/fake_device_watcher_winrt.h", "bluetooth/test/fake_gatt_characteristic_winrt.cc", "bluetooth/test/fake_gatt_characteristic_winrt.h", "bluetooth/test/fake_gatt_characteristics_result_winrt.cc", "bluetooth/test/fake_gatt_characteristics_result_winrt.h", + "bluetooth/test/fake_gatt_descriptor_winrt.cc", + "bluetooth/test/fake_gatt_descriptor_winrt.h", + "bluetooth/test/fake_gatt_descriptors_result_winrt.cc", + "bluetooth/test/fake_gatt_descriptors_result_winrt.h", "bluetooth/test/fake_gatt_device_service_winrt.cc", "bluetooth/test/fake_gatt_device_service_winrt.h", "bluetooth/test/fake_gatt_device_services_result_winrt.cc", "bluetooth/test/fake_gatt_device_services_result_winrt.h", + "bluetooth/test/fake_gatt_read_result_winrt.cc", + "bluetooth/test/fake_gatt_read_result_winrt.h", + "bluetooth/test/fake_gatt_value_changed_event_args_winrt.cc", + "bluetooth/test/fake_gatt_value_changed_event_args_winrt.h", + "bluetooth/test/fake_gatt_write_result_winrt.cc", + "bluetooth/test/fake_gatt_write_result_winrt.h", "bluetooth/test/fake_radio_winrt.cc", "bluetooth/test/fake_radio_winrt.h", ] diff --git a/chromium/device/base/device_monitor_linux.h b/chromium/device/base/device_monitor_linux.h index 24b57ea26ab..319cefed861 100644 --- a/chromium/device/base/device_monitor_linux.h +++ b/chromium/device/base/device_monitor_linux.h @@ -54,7 +54,7 @@ class DEVICE_BASE_EXPORT DeviceMonitorLinux { std::unique_ptr<base::FileDescriptorWatcher::Controller> monitor_watch_controller_; - base::ObserverList<Observer, true> observers_; + base::ObserverList<Observer, true>::Unchecked observers_; base::ThreadChecker thread_checker_; diff --git a/chromium/device/base/device_monitor_win.h b/chromium/device/base/device_monitor_win.h index 0370c79281f..0f46eb86123 100644 --- a/chromium/device/base/device_monitor_win.h +++ b/chromium/device/base/device_monitor_win.h @@ -42,7 +42,7 @@ class DEVICE_BASE_EXPORT DeviceMonitorWin { void NotifyDeviceRemoved(const GUID& class_guid, const std::string& device_path); - base::ObserverList<Observer> observer_list_; + base::ObserverList<Observer>::Unchecked observer_list_; }; } // namespace device diff --git a/chromium/device/base/features.cc b/chromium/device/base/features.cc index eaf9cd93ffb..164fe58d6fd 100644 --- a/chromium/device/base/features.cc +++ b/chromium/device/base/features.cc @@ -12,7 +12,7 @@ namespace device { const base::Feature kNewUsbBackend{"NewUsbBackend", base::FEATURE_DISABLED_BY_DEFAULT}; const base::Feature kNewBLEWinImplementation{"NewBLEWinImplementation", - base::FEATURE_DISABLED_BY_DEFAULT}; + base::FEATURE_ENABLED_BY_DEFAULT}; #endif // defined(OS_WIN) #if defined(OS_CHROMEOS) diff --git a/chromium/device/bluetooth/BUILD.gn b/chromium/device/bluetooth/BUILD.gn index b18288d0070..46cb42afd91 100644 --- a/chromium/device/bluetooth/BUILD.gn +++ b/chromium/device/bluetooth/BUILD.gn @@ -246,12 +246,18 @@ component("bluetooth") { sources += [ "bluetooth_adapter_winrt.cc", "bluetooth_adapter_winrt.h", + "bluetooth_advertisement_winrt.cc", + "bluetooth_advertisement_winrt.h", "bluetooth_device_winrt.cc", "bluetooth_device_winrt.h", "bluetooth_gatt_discoverer_winrt.cc", "bluetooth_gatt_discoverer_winrt.h", + "bluetooth_pairing_winrt.cc", + "bluetooth_pairing_winrt.h", "bluetooth_remote_gatt_characteristic_winrt.cc", "bluetooth_remote_gatt_characteristic_winrt.h", + "bluetooth_remote_gatt_descriptor_winrt.cc", + "bluetooth_remote_gatt_descriptor_winrt.h", "bluetooth_remote_gatt_service_winrt.cc", "bluetooth_remote_gatt_service_winrt.h", "event_utils_winrt.h", diff --git a/chromium/device/bluetooth/bluetooth_adapter.cc b/chromium/device/bluetooth/bluetooth_adapter.cc index 64fd5c8fe4b..54ec734b7be 100644 --- a/chromium/device/bluetooth/bluetooth_adapter.cc +++ b/chromium/device/bluetooth/bluetooth_adapter.cc @@ -58,6 +58,10 @@ bool BluetoothAdapter::HasObserver(BluetoothAdapter::Observer* observer) { return observers_.HasObserver(observer); } +bool BluetoothAdapter::CanPower() const { + return IsPresent(); +} + void BluetoothAdapter::SetPowered(bool powered, const base::Closure& callback, const ErrorCallback& error_callback) { @@ -193,6 +197,11 @@ BluetoothDevice::PairingDelegate* BluetoothAdapter::DefaultPairingDelegate() { return pairing_delegates_.front().first; } +std::vector<BluetoothAdvertisement*> +BluetoothAdapter::GetPendingAdvertisementsForTesting() const { + return {}; +} + void BluetoothAdapter::NotifyAdapterPoweredChanged(bool powered) { for (auto& observer : observers_) observer.AdapterPoweredChanged(this, powered); @@ -321,13 +330,12 @@ BluetoothAdapter::~BluetoothAdapter() { } } -void BluetoothAdapter::DidChangePoweredState() { +void BluetoothAdapter::RunPendingPowerCallbacks() { if (set_powered_callbacks_) { // Move into a local variable to clear out both callbacks at the end of the // scope and to allow scheduling another SetPowered() call in either of the // callbacks. - std::unique_ptr<SetPoweredCallbacks> callbacks = - std::move(set_powered_callbacks_); + auto callbacks = std::move(set_powered_callbacks_); callbacks->powered == IsPowered() ? std::move(callbacks->callback).Run() : callbacks->error_callback.Run(); } diff --git a/chromium/device/bluetooth/bluetooth_adapter.h b/chromium/device/bluetooth/bluetooth_adapter.h index b1399a64300..5129c45ff77 100644 --- a/chromium/device/bluetooth/bluetooth_adapter.h +++ b/chromium/device/bluetooth/bluetooth_adapter.h @@ -343,6 +343,11 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapter // is only considered present if the address has been obtained. virtual bool IsPresent() const = 0; + // Indicates whether the adapter radio can be powered. Defaults to + // IsPresent(). Currently only overridden on Windows, where the adapter can be + // present, but we might fail to get access to the underlying radio. + virtual bool CanPower() const; + // Indicates whether the adapter radio is powered. virtual bool IsPowered() const = 0; @@ -354,7 +359,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapter // callback based API. It will store pending callbacks in // |set_powered_callbacks_| and invoke SetPoweredImpl(bool) which these // platforms need to implement. Pending callbacks are only run when - // DidChangePoweredState() is invoked. + // RunPendingPowerCallbacks() is invoked. // // Platforms that natively support a callback based API (e.g. BlueZ and Win) // should override this method and provide their own implementation instead. @@ -522,6 +527,10 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapter const AdvertisementErrorCallback& error_callback) = 0; #endif + // Returns the list of pending advertisements that are not registered yet. + virtual std::vector<BluetoothAdvertisement*> + GetPendingAdvertisementsForTesting() const; + // Returns the local GATT services associated with this adapter with the // given identifier. Returns NULL if the service doesn't exist. virtual BluetoothLocalGattService* GetGattService( @@ -590,9 +599,9 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapter // pending SetPowered() callbacks need to be stored explicitly. virtual bool SetPoweredImpl(bool powered) = 0; - // Called by macOS and Android once the specific powered state events are - // received. Clears out pending callbacks. - void DidChangePoweredState(); + // Called by macOS, Android and WinRT once the specific powered state events + // are received or an error occurred. Clears out pending callbacks. + void RunPendingPowerCallbacks(); // Internal methods for initiating and terminating device discovery sessions. // An implementation of BluetoothAdapter keeps an internal reference count to @@ -684,7 +693,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapter scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_; // Observers of BluetoothAdapter, notified from implementation subclasses. - base::ObserverList<device::BluetoothAdapter::Observer> observers_; + base::ObserverList<device::BluetoothAdapter::Observer>::Unchecked observers_; // Devices paired with, connected to, discovered by, or visible to the // adapter. The key is the Bluetooth address of the device and the value is diff --git a/chromium/device/bluetooth/bluetooth_adapter_android.cc b/chromium/device/bluetooth/bluetooth_adapter_android.cc index ad1446ad689..1b6b29cdcb9 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_android.cc +++ b/chromium/device/bluetooth/bluetooth_adapter_android.cc @@ -150,7 +150,7 @@ void BluetoothAdapterAndroid::OnAdapterStateChanged( JNIEnv* env, const JavaParamRef<jobject>& caller, const bool powered) { - DidChangePoweredState(); + RunPendingPowerCallbacks(); NotifyAdapterPoweredChanged(powered); } diff --git a/chromium/device/bluetooth/bluetooth_adapter_factory.cc b/chromium/device/bluetooth/bluetooth_adapter_factory.cc index 8ec94920945..ab82c1f6519 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_factory.cc +++ b/chromium/device/bluetooth/bluetooth_adapter_factory.cc @@ -19,6 +19,7 @@ #endif #if defined(OS_WIN) #include "base/win/windows_version.h" +#include "device/bluetooth/bluetooth_adapter_win.h" #endif #if defined(ANDROID) #include "base/android/build_info.h" @@ -61,6 +62,26 @@ void RunAdapterCallbacks() { } #endif // defined(OS_WIN) || defined(OS_LINUX) +#if defined(OS_WIN) +// Shared classic adapter instance. See above why this is a lazy instance. +// Note: This is only applicable on Windows, as here the default adapter does +// not provide Bluetooth Classic support yet. +base::LazyInstance<base::WeakPtr<BluetoothAdapter>>::Leaky classic_adapter = + LAZY_INSTANCE_INITIALIZER; + +base::LazyInstance<AdapterCallbackList>::DestructorAtExit + classic_adapter_callbacks = LAZY_INSTANCE_INITIALIZER; + +void RunClassicAdapterCallbacks() { + DCHECK(classic_adapter.Get()); + scoped_refptr<BluetoothAdapter> adapter(classic_adapter.Get().get()); + for (auto& callback : classic_adapter_callbacks.Get()) + callback.Run(adapter); + + classic_adapter_callbacks.Get().clear(); +} +#endif // defined(OS_WIN) + } // namespace BluetoothAdapterFactory::~BluetoothAdapterFactory() = default; @@ -132,6 +153,31 @@ void BluetoothAdapterFactory::GetAdapter(const AdapterCallback& callback) { callback.Run(scoped_refptr<BluetoothAdapter>(default_adapter.Get().get())); } +// static +void BluetoothAdapterFactory::GetClassicAdapter( + const AdapterCallback& callback) { +#if defined(OS_WIN) + if (base::win::GetVersion() < base::win::VERSION_WIN10) { + // Prior to Win10, the default adapter will support Bluetooth classic. + GetAdapter(callback); + return; + } + + if (!classic_adapter.Get()) { + classic_adapter.Get() = BluetoothAdapterWin::CreateClassicAdapter( + base::Bind(&RunClassicAdapterCallbacks)); + DCHECK(!classic_adapter.Get()->IsInitialized()); + } + + if (!classic_adapter.Get()->IsInitialized()) + classic_adapter_callbacks.Get().push_back(callback); + else + callback.Run(scoped_refptr<BluetoothAdapter>(classic_adapter.Get().get())); +#else + GetAdapter(callback); +#endif // defined(OS_WIN) +} + #if defined(OS_LINUX) // static void BluetoothAdapterFactory::Shutdown() { @@ -144,6 +190,9 @@ void BluetoothAdapterFactory::Shutdown() { void BluetoothAdapterFactory::SetAdapterForTesting( scoped_refptr<BluetoothAdapter> adapter) { default_adapter.Get() = adapter->GetWeakPtrForTesting(); +#if defined(OS_WIN) + classic_adapter.Get() = adapter->GetWeakPtrForTesting(); +#endif } // static diff --git a/chromium/device/bluetooth/bluetooth_adapter_factory.h b/chromium/device/bluetooth/bluetooth_adapter_factory.h index 7217c2f7578..e82bad913f4 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_factory.h +++ b/chromium/device/bluetooth/bluetooth_adapter_factory.h @@ -52,6 +52,14 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterFactory { // use. static void GetAdapter(const AdapterCallback& callback); + // Returns the shared instance of the classic adapter, creating and + // initializing it if necessary. |callback| is called with the adapter + // instance passed only once the adapter is fully initialized and ready to + // use. + // For all platforms except Windows this is equivalent to calling + // GetAdapter(), as the default adapter already supports Bluetooth classic. + static void GetClassicAdapter(const AdapterCallback& callback); + #if defined(OS_LINUX) // Calls |BluetoothAdapter::Shutdown| on the adapter if // present. diff --git a/chromium/device/bluetooth/bluetooth_adapter_mac.mm b/chromium/device/bluetooth/bluetooth_adapter_mac.mm index 8b2092ec78f..24a63be8a4b 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_mac.mm +++ b/chromium/device/bluetooth/bluetooth_adapter_mac.mm @@ -157,6 +157,9 @@ BluetoothAdapterMac::~BluetoothAdapterMac() { // disconnect the gatt connection. To make sure they don't use the mac // adapter, they should be explicitly destroyed here. devices_.clear(); + // Explicitly clear out delegates, which might outlive the Adapter. + [low_energy_peripheral_manager_ setDelegate:nil]; + [low_energy_central_manager_ setDelegate:nil]; // Set low_energy_central_manager_ to nil so no devices will try to use it // while being destroyed after this method. |devices_| is owned by // BluetoothAdapter. @@ -515,7 +518,7 @@ void BluetoothAdapterMac::PollAdapter() { if (classic_powered_ != state.classic_powered) { classic_powered_ = state.classic_powered; - DidChangePoweredState(); + RunPendingPowerCallbacks(); NotifyAdapterPoweredChanged(classic_powered_); } diff --git a/chromium/device/bluetooth/bluetooth_adapter_unittest.cc b/chromium/device/bluetooth/bluetooth_adapter_unittest.cc index fe16beb92ea..274ca34a628 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_unittest.cc +++ b/chromium/device/bluetooth/bluetooth_adapter_unittest.cc @@ -25,6 +25,7 @@ #include "device/bluetooth/bluetooth_local_gatt_service.h" #include "device/bluetooth/test/bluetooth_test.h" #include "device/bluetooth/test/test_bluetooth_adapter_observer.h" +#include "device/bluetooth/test/test_bluetooth_advertisement_observer.h" #include "testing/gtest/include/gtest/gtest.h" #if defined(OS_ANDROID) @@ -44,6 +45,8 @@ using device::BluetoothDevice; namespace device { +namespace { + class TestBluetoothAdapter : public BluetoothAdapter { public: TestBluetoothAdapter() = default; @@ -176,6 +179,8 @@ class TestPairingDelegate : public BluetoothDevice::PairingDelegate { void AuthorizePairing(BluetoothDevice* device) override {} }; +} // namespace + TEST(BluetoothAdapterTest, NoDefaultPairingDelegate) { scoped_refptr<BluetoothAdapter> adapter = new TestBluetoothAdapter(); @@ -510,12 +515,31 @@ TEST_F(BluetoothTest, MAYBE_ConstructFakeAdapter) { InitWithFakeAdapter(); EXPECT_EQ(adapter_->GetAddress(), kTestAdapterAddress); EXPECT_EQ(adapter_->GetName(), kTestAdapterName); + EXPECT_TRUE(adapter_->CanPower()); EXPECT_TRUE(adapter_->IsPresent()); EXPECT_TRUE(adapter_->IsPowered()); EXPECT_FALSE(adapter_->IsDiscoverable()); EXPECT_FALSE(adapter_->IsDiscovering()); } +#if defined(OS_WIN) +TEST_P(BluetoothTestWinrtOnly, ConstructFakeAdapterWithoutRadio) { + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + + InitFakeAdapterWithoutRadio(); + EXPECT_EQ(adapter_->GetAddress(), kTestAdapterAddress); + EXPECT_EQ(adapter_->GetName(), kTestAdapterName); + EXPECT_TRUE(adapter_->IsPresent()); + EXPECT_FALSE(adapter_->CanPower()); + EXPECT_FALSE(adapter_->IsPowered()); + EXPECT_FALSE(adapter_->IsDiscoverable()); + EXPECT_FALSE(adapter_->IsDiscovering()); +} +#endif // defined(OS_WIN) + // TODO(scheib): Enable BluetoothTest fixture tests on all platforms. #if defined(OS_ANDROID) #define MAYBE_DiscoverySession DiscoverySession @@ -753,6 +777,88 @@ TEST_F(BluetoothTest, MAYBE_DiscoverMultipleLowEnergyDevices) { EXPECT_EQ(2u, adapter_->GetDevices().size()); } +#if defined(OS_WIN) +// Tests that the adapter responds to external changes to the power state. +TEST_P(BluetoothTestWinrtOnly, SimulateAdapterPoweredOffAndOn) { + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + + InitWithFakeAdapter(); + TestBluetoothAdapterObserver observer(adapter_); + + ASSERT_TRUE(adapter_->IsPresent()); + ASSERT_TRUE(adapter_->IsPowered()); + EXPECT_EQ(0, observer.powered_changed_count()); + + SimulateAdapterPoweredOff(); + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(adapter_->IsPowered()); + EXPECT_EQ(1, observer.powered_changed_count()); + EXPECT_FALSE(observer.last_powered()); + + SimulateAdapterPoweredOn(); + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(adapter_->IsPowered()); + EXPECT_EQ(2, observer.powered_changed_count()); + EXPECT_TRUE(observer.last_powered()); +} + +// Tests that the adapter responds to external changes to the power state, even +// if it failed to obtain the underlying radio. +TEST_P(BluetoothTestWinrtOnly, SimulateAdapterPoweredOnAndOffWithoutRadio) { + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + + InitFakeAdapterWithoutRadio(); + TestBluetoothAdapterObserver observer(adapter_); + + ASSERT_TRUE(adapter_->IsPresent()); + ASSERT_FALSE(adapter_->IsPowered()); + EXPECT_EQ(0, observer.powered_changed_count()); + + SimulateAdapterPoweredOn(); + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(adapter_->IsPowered()); + EXPECT_EQ(1, observer.powered_changed_count()); + EXPECT_TRUE(observer.last_powered()); + + SimulateAdapterPoweredOff(); + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(adapter_->IsPowered()); + EXPECT_EQ(2, observer.powered_changed_count()); + EXPECT_FALSE(observer.last_powered()); +} + +// Makes sure the error callback gets run when changing the adapter power state +// fails. +// TODO(https://crbug.com/878680): Implement SimulateAdapterPowerSuccess() and +// enable on all platforms. +TEST_P(BluetoothTestWinrtOnly, SimulateAdapterPowerFailure) { + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + + InitWithFakeAdapter(); + ASSERT_TRUE(adapter_->IsPresent()); + ASSERT_TRUE(adapter_->IsPowered()); + + adapter_->SetPowered(false, GetCallback(Call::NOT_EXPECTED), + GetErrorCallback(Call::EXPECTED)); + SimulateAdapterPowerFailure(); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(adapter_->IsPowered()); +} +#endif // defined(OS_WIN) + // TODO(https://crbug.com/804356): Enable this test on old Windows versions as // well. #if defined(OS_WIN) @@ -1042,6 +1148,316 @@ TEST_F(BluetoothTest, MAYBE_TurnOffAdapterWithConnectedDevice) { EXPECT_FALSE(device->IsGattConnected()); } +#if defined(OS_WIN) +TEST_P(BluetoothTestWinrtOnly, RegisterAdvertisement) { + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + + auto advertisement_data = std::make_unique<BluetoothAdvertisement::Data>( + BluetoothAdvertisement::ADVERTISEMENT_TYPE_BROADCAST); + advertisement_data->set_manufacturer_data( + std::make_unique<BluetoothAdvertisement::ManufacturerData>()); + + InitWithFakeAdapter(); + adapter_->RegisterAdvertisement( + std::move(advertisement_data), + GetCreateAdvertisementCallback(Call::EXPECTED), + GetAdvertisementErrorCallback(Call::NOT_EXPECTED)); + auto pending_advertisements = adapter_->GetPendingAdvertisementsForTesting(); + ASSERT_FALSE(pending_advertisements.empty()); + SimulateAdvertisementStarted(pending_advertisements[0]); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(adapter_->GetPendingAdvertisementsForTesting().empty()); +} + +TEST_P(BluetoothTestWinrtOnly, FailRegisterAdvertisement) { + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + + auto advertisement_data = std::make_unique<BluetoothAdvertisement::Data>( + BluetoothAdvertisement::ADVERTISEMENT_TYPE_BROADCAST); + advertisement_data->set_manufacturer_data( + std::make_unique<BluetoothAdvertisement::ManufacturerData>()); + + InitWithFakeAdapter(); + adapter_->RegisterAdvertisement( + std::move(advertisement_data), + GetCreateAdvertisementCallback(Call::NOT_EXPECTED), + GetAdvertisementErrorCallback(Call::EXPECTED)); + auto pending_advertisements = adapter_->GetPendingAdvertisementsForTesting(); + ASSERT_FALSE(pending_advertisements.empty()); + SimulateAdvertisementError(pending_advertisements[0], + BluetoothAdvertisement::ERROR_ADAPTER_POWERED_OFF); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(BluetoothAdvertisement::ERROR_ADAPTER_POWERED_OFF, + last_advertisement_error_code_); + EXPECT_TRUE(adapter_->GetPendingAdvertisementsForTesting().empty()); +} + +TEST_P(BluetoothTestWinrtOnly, RegisterAndUnregisterAdvertisement) { + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + + auto advertisement_data = std::make_unique<BluetoothAdvertisement::Data>( + BluetoothAdvertisement::ADVERTISEMENT_TYPE_BROADCAST); + advertisement_data->set_manufacturer_data( + std::make_unique<BluetoothAdvertisement::ManufacturerData>()); + + InitWithFakeAdapter(); + adapter_->RegisterAdvertisement( + std::move(advertisement_data), + GetCreateAdvertisementCallback(Call::EXPECTED), + GetAdvertisementErrorCallback(Call::NOT_EXPECTED)); + auto pending_advertisements = adapter_->GetPendingAdvertisementsForTesting(); + ASSERT_FALSE(pending_advertisements.empty()); + auto* advertisement = pending_advertisements[0]; + SimulateAdvertisementStarted(advertisement); + base::RunLoop().RunUntilIdle(); + + TestBluetoothAdvertisementObserver observer(advertisement); + advertisement->Unregister(GetCallback(Call::EXPECTED), + GetAdvertisementErrorCallback(Call::NOT_EXPECTED)); + SimulateAdvertisementStopped(advertisement); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(observer.released()); + EXPECT_EQ(1u, observer.released_count()); + EXPECT_TRUE(adapter_->GetPendingAdvertisementsForTesting().empty()); +} + +TEST_P(BluetoothTestWinrtOnly, FailUnregisterAdvertisement) { + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + + auto advertisement_data = std::make_unique<BluetoothAdvertisement::Data>( + BluetoothAdvertisement::ADVERTISEMENT_TYPE_BROADCAST); + advertisement_data->set_manufacturer_data( + std::make_unique<BluetoothAdvertisement::ManufacturerData>()); + + InitWithFakeAdapter(); + adapter_->RegisterAdvertisement( + std::move(advertisement_data), + GetCreateAdvertisementCallback(Call::EXPECTED), + GetAdvertisementErrorCallback(Call::NOT_EXPECTED)); + auto pending_advertisements = adapter_->GetPendingAdvertisementsForTesting(); + ASSERT_FALSE(pending_advertisements.empty()); + auto* advertisement = pending_advertisements[0]; + SimulateAdvertisementStarted(advertisement); + base::RunLoop().RunUntilIdle(); + + TestBluetoothAdvertisementObserver observer(advertisement); + advertisement->Unregister(GetCallback(Call::NOT_EXPECTED), + GetAdvertisementErrorCallback(Call::EXPECTED)); + SimulateAdvertisementError(advertisement, + BluetoothAdvertisement::ERROR_RESET_ADVERTISING); + base::RunLoop().RunUntilIdle(); + + // Expect no change to the observer status. + EXPECT_FALSE(observer.released()); + EXPECT_EQ(0u, observer.released_count()); + EXPECT_EQ(BluetoothAdvertisement::ERROR_RESET_ADVERTISING, + last_advertisement_error_code_); + EXPECT_TRUE(adapter_->GetPendingAdvertisementsForTesting().empty()); +} + +TEST_P(BluetoothTestWinrtOnly, RegisterAdvertisementWithInvalidData) { + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + + // WinRT only accepts ManufacturerData in the payload, other data should be + // rejected. + auto advertisement_data = std::make_unique<BluetoothAdvertisement::Data>( + BluetoothAdvertisement::ADVERTISEMENT_TYPE_BROADCAST); + advertisement_data->set_service_data( + std::make_unique<BluetoothAdvertisement::ServiceData>()); + + InitWithFakeAdapter(); + adapter_->RegisterAdvertisement( + std::move(advertisement_data), + GetCreateAdvertisementCallback(Call::NOT_EXPECTED), + GetAdvertisementErrorCallback(Call::EXPECTED)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(BluetoothAdvertisement::ERROR_STARTING_ADVERTISEMENT, + last_advertisement_error_code_); + EXPECT_TRUE(adapter_->GetPendingAdvertisementsForTesting().empty()); +} + +TEST_P(BluetoothTestWinrtOnly, RegisterMultipleAdvertisements) { + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + + InitWithFakeAdapter(); + constexpr size_t kNumAdvertisements = 10u; + + for (size_t i = 0; i < kNumAdvertisements; ++i) { + auto advertisement_data = std::make_unique<BluetoothAdvertisement::Data>( + BluetoothAdvertisement::ADVERTISEMENT_TYPE_BROADCAST); + advertisement_data->set_manufacturer_data( + std::make_unique<BluetoothAdvertisement::ManufacturerData>()); + + adapter_->RegisterAdvertisement( + std::move(advertisement_data), + GetCreateAdvertisementCallback(Call::EXPECTED), + GetAdvertisementErrorCallback(Call::NOT_EXPECTED)); + } + + base::RunLoop().RunUntilIdle(); + auto pending_advertisements = adapter_->GetPendingAdvertisementsForTesting(); + ASSERT_EQ(kNumAdvertisements, pending_advertisements.size()); + for (size_t i = 0; i < kNumAdvertisements; ++i) + SimulateAdvertisementStarted(pending_advertisements[i]); + + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(adapter_->GetPendingAdvertisementsForTesting().empty()); +} + +TEST_P(BluetoothTestWinrtOnly, UnregisterAdvertisementWhilePendingUnregister) { + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + + InitWithFakeAdapter(); + auto advertisement_data = std::make_unique<BluetoothAdvertisement::Data>( + BluetoothAdvertisement::ADVERTISEMENT_TYPE_BROADCAST); + advertisement_data->set_manufacturer_data( + std::make_unique<BluetoothAdvertisement::ManufacturerData>()); + + adapter_->RegisterAdvertisement( + std::move(advertisement_data), + GetCreateAdvertisementCallback(Call::EXPECTED), + GetAdvertisementErrorCallback(Call::NOT_EXPECTED)); + + base::RunLoop().RunUntilIdle(); + auto pending_advertisements = adapter_->GetPendingAdvertisementsForTesting(); + ASSERT_EQ(1u, pending_advertisements.size()); + auto* advertisement = pending_advertisements[0]; + SimulateAdvertisementStarted(advertisement); + base::RunLoop().RunUntilIdle(); + + TestBluetoothAdvertisementObserver observer(advertisement); + advertisement->Unregister(GetCallback(Call::EXPECTED), + GetAdvertisementErrorCallback(Call::NOT_EXPECTED)); + + // Schedule another Unregister, which is expected to fail. + advertisement->Unregister(GetCallback(Call::NOT_EXPECTED), + GetAdvertisementErrorCallback(Call::EXPECTED)); + base::RunLoop().RunUntilIdle(); + // Expect no change to the observer status. + EXPECT_FALSE(observer.released()); + EXPECT_EQ(0u, observer.released_count()); + EXPECT_EQ(BluetoothAdvertisement::ERROR_RESET_ADVERTISING, + last_advertisement_error_code_); + + // Simulate success of the first unregistration. + SimulateAdvertisementStopped(advertisement); + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(observer.released()); + EXPECT_EQ(1u, observer.released_count()); + EXPECT_TRUE(adapter_->GetPendingAdvertisementsForTesting().empty()); +} + +TEST_P(BluetoothTestWinrtOnly, DoubleUnregisterAdvertisement) { + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + + InitWithFakeAdapter(); + auto advertisement_data = std::make_unique<BluetoothAdvertisement::Data>( + BluetoothAdvertisement::ADVERTISEMENT_TYPE_BROADCAST); + advertisement_data->set_manufacturer_data( + std::make_unique<BluetoothAdvertisement::ManufacturerData>()); + + adapter_->RegisterAdvertisement( + std::move(advertisement_data), + GetCreateAdvertisementCallback(Call::EXPECTED), + GetAdvertisementErrorCallback(Call::NOT_EXPECTED)); + + base::RunLoop().RunUntilIdle(); + auto pending_advertisements = adapter_->GetPendingAdvertisementsForTesting(); + ASSERT_EQ(1u, pending_advertisements.size()); + auto* advertisement = pending_advertisements[0]; + SimulateAdvertisementStarted(advertisement); + base::RunLoop().RunUntilIdle(); + + // Perform two unregistrations after each other. Both should succeed. + TestBluetoothAdvertisementObserver observer(advertisement); + advertisement->Unregister(GetCallback(Call::EXPECTED), + GetAdvertisementErrorCallback(Call::NOT_EXPECTED)); + SimulateAdvertisementStopped(advertisement); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(observer.released()); + EXPECT_EQ(1u, observer.released_count()); + + advertisement->Unregister(GetCallback(Call::EXPECTED), + GetAdvertisementErrorCallback(Call::NOT_EXPECTED)); + SimulateAdvertisementStopped(advertisement); + base::RunLoop().RunUntilIdle(); + // The second unregister is a no-op, and should not notify observers again. + EXPECT_TRUE(observer.released()); + EXPECT_EQ(1u, observer.released_count()); + + EXPECT_TRUE(adapter_->GetPendingAdvertisementsForTesting().empty()); +} + +TEST_P(BluetoothTestWinrtOnly, SimulateAdvertisementStoppedByOS) { + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + + InitWithFakeAdapter(); + auto advertisement_data = std::make_unique<BluetoothAdvertisement::Data>( + BluetoothAdvertisement::ADVERTISEMENT_TYPE_BROADCAST); + advertisement_data->set_manufacturer_data( + std::make_unique<BluetoothAdvertisement::ManufacturerData>()); + + adapter_->RegisterAdvertisement( + std::move(advertisement_data), + GetCreateAdvertisementCallback(Call::EXPECTED), + GetAdvertisementErrorCallback(Call::NOT_EXPECTED)); + + base::RunLoop().RunUntilIdle(); + auto pending_advertisements = adapter_->GetPendingAdvertisementsForTesting(); + ASSERT_EQ(1u, pending_advertisements.size()); + auto* advertisement = pending_advertisements[0]; + SimulateAdvertisementStarted(advertisement); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(adapter_->GetPendingAdvertisementsForTesting().empty()); + + TestBluetoothAdvertisementObserver observer(advertisement); + // Simulate the OS stopping the advertisement. This should notify the + // |observer|. + SimulateAdvertisementStopped(advertisement); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(observer.released()); + EXPECT_EQ(1u, observer.released_count()); + + // While Unregister() is a no-op now, we still expect an invocation of the + // success callback, but no change to the |observer| state. + advertisement->Unregister(GetCallback(Call::EXPECTED), + GetAdvertisementErrorCallback(Call::NOT_EXPECTED)); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(observer.released()); + EXPECT_EQ(1u, observer.released_count()); +} + +#endif // defined(OS_WIN) + #if (defined(OS_CHROMEOS) || defined(OS_LINUX)) && \ !defined(USE_CAST_BLUETOOTH_ADAPTER) #define MAYBE_RegisterLocalGattServices RegisterLocalGattServices diff --git a/chromium/device/bluetooth/bluetooth_adapter_win.cc b/chromium/device/bluetooth/bluetooth_adapter_win.cc index 213a76c28b3..22686c18fbf 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_win.cc +++ b/chromium/device/bluetooth/bluetooth_adapter_win.cc @@ -44,6 +44,12 @@ base::WeakPtr<BluetoothAdapter> BluetoothAdapterWin::CreateAdapter( return adapter->weak_ptr_factory_.GetWeakPtr(); } + return BluetoothAdapterWin::CreateClassicAdapter(std::move(init_callback)); +} + +// static +base::WeakPtr<BluetoothAdapter> BluetoothAdapterWin::CreateClassicAdapter( + InitCallback init_callback) { auto* adapter = new BluetoothAdapterWin(std::move(init_callback)); adapter->Init(); return adapter->weak_ptr_factory_.GetWeakPtr(); diff --git a/chromium/device/bluetooth/bluetooth_adapter_win.h b/chromium/device/bluetooth/bluetooth_adapter_win.h index dd996e8878e..c391eca773d 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_win.h +++ b/chromium/device/bluetooth/bluetooth_adapter_win.h @@ -36,6 +36,9 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterWin static base::WeakPtr<BluetoothAdapter> CreateAdapter( InitCallback init_callback); + static base::WeakPtr<BluetoothAdapter> CreateClassicAdapter( + InitCallback init_callback); + static bool UseNewBLEWinImplementation(); // BluetoothAdapter: diff --git a/chromium/device/bluetooth/bluetooth_adapter_winrt.cc b/chromium/device/bluetooth/bluetooth_adapter_winrt.cc index 7660b563075..f53da79c97d 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_winrt.cc +++ b/chromium/device/bluetooth/bluetooth_adapter_winrt.cc @@ -6,23 +6,30 @@ #include <windows.foundation.collections.h> #include <windows.foundation.h> +#include <windows.storage.streams.h> #include <wrl/event.h> #include <memory> #include <utility> +#include <vector> #include "base/bind.h" +#include "base/bind_helpers.h" #include "base/callback.h" #include "base/callback_helpers.h" #include "base/containers/span.h" #include "base/logging.h" #include "base/memory/ptr_util.h" +#include "base/memory/scoped_refptr.h" #include "base/single_thread_task_runner.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" #include "base/strings/string_piece.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/thread_task_runner_handle.h" #include "base/win/core_winrt_util.h" +#include "device/bluetooth/bluetooth_advertisement_winrt.h" #include "device/bluetooth/bluetooth_device_winrt.h" #include "device/bluetooth/bluetooth_discovery_filter.h" #include "device/bluetooth/bluetooth_discovery_session_outcome.h" @@ -38,23 +45,37 @@ namespace uwp { using ABI::Windows::Devices::Bluetooth::BluetoothAdapter; } // namespace uwp using ABI::Windows::Devices::Bluetooth::Advertisement:: + BluetoothLEAdvertisementDataSection; +using ABI::Windows::Devices::Bluetooth::Advertisement:: + BluetoothLEAdvertisementFlags; +using ABI::Windows::Devices::Bluetooth::Advertisement:: BluetoothLEAdvertisementWatcherStatus; using ABI::Windows::Devices::Bluetooth::Advertisement:: BluetoothLEAdvertisementWatcherStatus_Aborted; +using ABI::Windows::Devices::Bluetooth::Advertisement:: + BluetoothLEManufacturerData; using ABI::Windows::Devices::Bluetooth::Advertisement::BluetoothLEScanningMode; using ABI::Windows::Devices::Bluetooth::Advertisement:: BluetoothLEScanningMode_Active; using ABI::Windows::Devices::Bluetooth::Advertisement:: IBluetoothLEAdvertisement; using ABI::Windows::Devices::Bluetooth::Advertisement:: + IBluetoothLEAdvertisementDataSection; +using ABI::Windows::Devices::Bluetooth::Advertisement:: + IBluetoothLEAdvertisementPublisherFactory; +using ABI::Windows::Devices::Bluetooth::Advertisement:: IBluetoothLEAdvertisementReceivedEventArgs; using ABI::Windows::Devices::Bluetooth::Advertisement:: IBluetoothLEAdvertisementWatcher; +using ABI::Windows::Devices::Bluetooth::Advertisement:: + IBluetoothLEManufacturerData; using ABI::Windows::Devices::Bluetooth::IBluetoothAdapter; using ABI::Windows::Devices::Bluetooth::IBluetoothAdapterStatics; using ABI::Windows::Devices::Enumeration::DeviceInformation; using ABI::Windows::Devices::Enumeration::IDeviceInformation; using ABI::Windows::Devices::Enumeration::IDeviceInformationStatics; +using ABI::Windows::Devices::Enumeration::IDeviceInformationUpdate; +using ABI::Windows::Devices::Enumeration::IDeviceWatcher; using ABI::Windows::Devices::Radios::IRadio; using ABI::Windows::Devices::Radios::IRadioStatics; using ABI::Windows::Devices::Radios::Radio; @@ -67,7 +88,13 @@ using ABI::Windows::Devices::Radios::RadioState; using ABI::Windows::Devices::Radios::RadioState_Off; using ABI::Windows::Devices::Radios::RadioState_On; using ABI::Windows::Foundation::Collections::IVector; +using ABI::Windows::Foundation::Collections::IVectorView; using ABI::Windows::Foundation::IAsyncOperation; +using ABI::Windows::Foundation::IReference; +using ABI::Windows::Storage::Streams::IBuffer; +using ABI::Windows::Storage::Streams::IDataReader; +using ABI::Windows::Storage::Streams::IDataReaderStatics; +using Microsoft::WRL::Callback; using Microsoft::WRL::ComPtr; bool ResolveCoreWinRT() { @@ -75,6 +102,15 @@ bool ResolveCoreWinRT() { base::win::ScopedHString::ResolveCoreWinRTStringDelayload(); } +// Query string for powered Bluetooth radios. GUID Reference: +// https://docs.microsoft.com/en-us/windows-hardware/drivers/install/guid-bthport-device-interface +// TODO(https://crbug.com/821766): Consider adding WindowsCreateStringReference +// to base::win::ScopedHString to avoid allocating memory for this string. +constexpr wchar_t kPoweredRadiosAqsFilter[] = + L"System.Devices.InterfaceClassGuid:=\"{0850302A-B344-4fda-9BE9-" + L"90576B8D46F0}\" AND " + L"System.Devices.InterfaceEnabled:=System.StructuredQueryType.Boolean#True"; + // Utility functions to pretty print enum values. constexpr const char* ToCString(RadioAccessStatus access_status) { switch (access_status) { @@ -92,37 +128,285 @@ constexpr const char* ToCString(RadioAccessStatus access_status) { return ""; } -base::Optional<BluetoothDevice::UUIDList> ExtractAdvertisedUUIDs( +template <typename VectorView, typename T> +bool ToStdVector(VectorView* view, std::vector<T>* vector) { + unsigned size; + HRESULT hr = view->get_Size(&size); + if (FAILED(hr)) { + VLOG(2) << "get_Size() failed: " << logging::SystemErrorCodeToString(hr); + return false; + } + + vector->resize(size); + for (size_t i = 0; i < size; ++i) { + hr = view->GetAt(i, &(*vector)[i]); + DCHECK(SUCCEEDED(hr)) << "GetAt(" << i << ") failed: " + << logging::SystemErrorCodeToString(hr); + } + + return true; +} + +base::Optional<std::vector<uint8_t>> ExtractVector(IBuffer* buffer) { + ComPtr<IDataReaderStatics> data_reader_statics; + HRESULT hr = base::win::GetActivationFactory< + IDataReaderStatics, RuntimeClass_Windows_Storage_Streams_DataReader>( + &data_reader_statics); + if (FAILED(hr)) { + VLOG(2) << "Getting DataReaderStatics Activation Factory failed: " + << logging::SystemErrorCodeToString(hr); + return base::nullopt; + } + + ComPtr<IDataReader> data_reader; + hr = data_reader_statics->FromBuffer(buffer, &data_reader); + if (FAILED(hr)) { + VLOG(2) << "FromBuffer() failed: " << logging::SystemErrorCodeToString(hr); + return base::nullopt; + } + + uint32_t buffer_length; + hr = buffer->get_Length(&buffer_length); + if (FAILED(hr)) { + VLOG(2) << "get_Length() failed: " << logging::SystemErrorCodeToString(hr); + return base::nullopt; + } + + std::vector<uint8_t> bytes(buffer_length); + hr = data_reader->ReadBytes(buffer_length, bytes.data()); + if (FAILED(hr)) { + VLOG(2) << "ReadBytes() failed: " << logging::SystemErrorCodeToString(hr); + return base::nullopt; + } + + return bytes; +} + +base::Optional<uint8_t> ExtractFlags(IBluetoothLEAdvertisement* advertisement) { + if (!advertisement) + return base::nullopt; + + ComPtr<IReference<BluetoothLEAdvertisementFlags>> flags_ref; + HRESULT hr = advertisement->get_Flags(&flags_ref); + if (FAILED(hr)) { + VLOG(2) << "get_Flags() failed: " << logging::SystemErrorCodeToString(hr); + return base::nullopt; + } + + if (!flags_ref) { + VLOG(2) << "No advertisement flags found."; + return base::nullopt; + } + + BluetoothLEAdvertisementFlags flags; + hr = flags_ref->get_Value(&flags); + if (FAILED(hr)) { + VLOG(2) << "get_Value() failed: " << logging::SystemErrorCodeToString(hr); + return base::nullopt; + } + + return flags; +} + +BluetoothDevice::UUIDList ExtractAdvertisedUUIDs( IBluetoothLEAdvertisement* advertisement) { + if (!advertisement) + return {}; + ComPtr<IVector<GUID>> service_uuids; HRESULT hr = advertisement->get_ServiceUuids(&service_uuids); if (FAILED(hr)) { VLOG(2) << "get_ServiceUuids() failed: " << logging::SystemErrorCodeToString(hr); - return base::nullopt; + return {}; } - unsigned num_service_uuids; - hr = service_uuids->get_Size(&num_service_uuids); + std::vector<GUID> guids; + if (!ToStdVector(service_uuids.Get(), &guids)) + return {}; + + BluetoothDevice::UUIDList advertised_uuids; + advertised_uuids.reserve(guids.size()); + for (const auto& guid : guids) + advertised_uuids.emplace_back(guid); + + return advertised_uuids; +} + +// This method populates service data for a particular sized UUID. Given the +// lack of tailored platform APIs, we need to parse the raw advertisement data +// sections ourselves. These data sections are effectively a list of blobs, +// where each blob starts with the corresponding UUID in little endian order, +// followed by the corresponding service data. +void PopulateServiceData( + BluetoothDevice::ServiceDataMap* service_data, + const std::vector<ComPtr<IBluetoothLEAdvertisementDataSection>>& + data_sections, + size_t num_bytes_uuid) { + for (const auto& data_section : data_sections) { + ComPtr<IBuffer> buffer; + HRESULT hr = data_section->get_Data(&buffer); + if (FAILED(hr)) { + VLOG(2) << "get_Data() failed: " << logging::SystemErrorCodeToString(hr); + continue; + } + + auto bytes = ExtractVector(buffer.Get()); + if (!bytes) + continue; + + auto bytes_span = base::make_span(*bytes); + if (bytes_span.size() < num_bytes_uuid) { + VLOG(2) << "Buffer Length is too small: " << bytes_span.size() << " vs. " + << num_bytes_uuid; + continue; + } + + auto uuid_span = bytes_span.first(num_bytes_uuid); + // The UUID is specified in little endian format, thus we reverse the bytes + // here. + std::vector<uint8_t> uuid_bytes(uuid_span.rbegin(), uuid_span.rend()); + + // HexEncode the bytes and add dashes as required. + std::string uuid_str; + for (char c : base::HexEncode(uuid_bytes.data(), uuid_bytes.size())) { + const size_t size = uuid_str.size(); + if (size == 8 || size == 13 || size == 18 || size == 23) + uuid_str.push_back('-'); + uuid_str.push_back(c); + } + + auto service_data_span = bytes_span.subspan(num_bytes_uuid); + auto result = service_data->emplace( + BluetoothUUID(uuid_str), std::vector<uint8_t>(service_data_span.begin(), + service_data_span.end())); + // Check that an insertion happened. + DCHECK(result.second); + // Check that the inserted UUID is valid. + DCHECK(result.first->first.IsValid()); + } +} + +BluetoothDevice::ServiceDataMap ExtractServiceData( + IBluetoothLEAdvertisement* advertisement) { + BluetoothDevice::ServiceDataMap service_data; + if (!advertisement) + return service_data; + + static constexpr std::pair<uint8_t, size_t> kServiceDataTypesAndNumBits[] = { + {BluetoothDeviceWinrt::k16BitServiceDataSection, 16}, + {BluetoothDeviceWinrt::k32BitServiceDataSection, 32}, + {BluetoothDeviceWinrt::k128BitServiceDataSection, 128}, + }; + + for (const auto& data_type_and_num_bits : kServiceDataTypesAndNumBits) { + ComPtr<IVectorView<BluetoothLEAdvertisementDataSection*>> data_sections; + HRESULT hr = advertisement->GetSectionsByType(data_type_and_num_bits.first, + &data_sections); + if (FAILED(hr)) { + VLOG(2) << "GetSectionsByType() failed: " + << logging::SystemErrorCodeToString(hr); + continue; + } + + std::vector<ComPtr<IBluetoothLEAdvertisementDataSection>> vector; + if (!ToStdVector(data_sections.Get(), &vector)) + continue; + + PopulateServiceData(&service_data, vector, + data_type_and_num_bits.second / 8); + } + + return service_data; +} + +BluetoothDevice::ManufacturerDataMap ExtractManufacturerData( + IBluetoothLEAdvertisement* advertisement) { + if (!advertisement) + return {}; + + ComPtr<IVector<BluetoothLEManufacturerData*>> manufacturer_data_ptr; + HRESULT hr = advertisement->get_ManufacturerData(&manufacturer_data_ptr); if (FAILED(hr)) { - VLOG(2) << "get_Size() failed: " << logging::SystemErrorCodeToString(hr); - return base::nullopt; + VLOG(2) << "GetManufacturerData() failed: " + << logging::SystemErrorCodeToString(hr); + return {}; } - BluetoothDevice::UUIDList advertised_uuids; - for (size_t i = 0; i < num_service_uuids; ++i) { - GUID service_uuid; - hr = service_uuids->GetAt(i, &service_uuid); + std::vector<ComPtr<IBluetoothLEManufacturerData>> manufacturer_data; + if (!ToStdVector(manufacturer_data_ptr.Get(), &manufacturer_data)) + return {}; + + BluetoothDevice::ManufacturerDataMap manufacturer_data_map; + for (const auto& manufacturer_datum : manufacturer_data) { + uint16_t company_id; + hr = manufacturer_datum->get_CompanyId(&company_id); if (FAILED(hr)) { - VLOG(2) << "GetAt(" << i - << ") failed: " << logging::SystemErrorCodeToString(hr); - return base::nullopt; + VLOG(2) << "get_CompanyId() failed: " + << logging::SystemErrorCodeToString(hr); + continue; } - advertised_uuids.emplace_back(service_uuid); + ComPtr<IBuffer> buffer; + hr = manufacturer_datum->get_Data(&buffer); + if (FAILED(hr)) { + VLOG(2) << "get_Data() failed: " << logging::SystemErrorCodeToString(hr); + continue; + } + + auto bytes = ExtractVector(buffer.Get()); + if (!bytes) + continue; + + manufacturer_data_map.emplace(company_id, std::move(*bytes)); } - return advertised_uuids; + return manufacturer_data_map; +} + +// Similarly to extracting the service data Windows does not provide a specific +// API to extract the tx power. Thus we also parse the raw data sections here. +// If present, we expect a single entry for tx power with a blob of size 1 byte. +base::Optional<int8_t> ExtractTxPower( + IBluetoothLEAdvertisement* advertisement) { + if (!advertisement) + return base::nullopt; + + ComPtr<IVectorView<BluetoothLEAdvertisementDataSection*>> data_sections; + HRESULT hr = advertisement->GetSectionsByType( + BluetoothDeviceWinrt::kTxPowerLevelDataSection, &data_sections); + if (FAILED(hr)) { + VLOG(2) << "GetSectionsByType() failed: " + << logging::SystemErrorCodeToString(hr); + return base::nullopt; + } + + std::vector<ComPtr<IBluetoothLEAdvertisementDataSection>> vector; + if (!ToStdVector(data_sections.Get(), &vector) || vector.empty()) + return base::nullopt; + + if (vector.size() != 1u) { + VLOG(2) << "Unexpected number of data sections: " << vector.size(); + return base::nullopt; + } + + ComPtr<IBuffer> buffer; + hr = vector.front()->get_Data(&buffer); + if (FAILED(hr)) { + VLOG(2) << "get_Data() failed: " << logging::SystemErrorCodeToString(hr); + return base::nullopt; + } + + auto bytes = ExtractVector(buffer.Get()); + if (!bytes) + return base::nullopt; + + if (bytes->size() != 1) { + VLOG(2) << "Unexpected number of bytes: " << bytes->size(); + return base::nullopt; + } + + return bytes->front(); } ComPtr<IBluetoothLEAdvertisement> GetAdvertisement( @@ -157,28 +441,19 @@ base::Optional<std::string> GetDeviceName( void ExtractAndUpdateAdvertisementData( IBluetoothLEAdvertisementReceivedEventArgs* received, BluetoothDevice* device) { - int16_t rssi; + int16_t rssi = 0; HRESULT hr = received->get_RawSignalStrengthInDBm(&rssi); if (FAILED(hr)) { VLOG(2) << "get_RawSignalStrengthInDBm() failed: " << logging::SystemErrorCodeToString(hr); - return; } ComPtr<IBluetoothLEAdvertisement> advertisement = GetAdvertisement(received); - if (!advertisement) - return; - - auto advertised_uuids = ExtractAdvertisedUUIDs(advertisement.Get()); - if (!advertised_uuids) - return; - - // TODO(https://crbug.com/821766): Implement extraction of flags, tx power, - // service data and manufacturer data. - device->UpdateAdvertisementData( - rssi, base::nullopt /* flags */, std::move(*advertised_uuids), - base::nullopt /* tx_power */, BluetoothDevice::ServiceDataMap(), - BluetoothDevice::ManufacturerDataMap()); + device->UpdateAdvertisementData(rssi, ExtractFlags(advertisement.Get()), + ExtractAdvertisedUUIDs(advertisement.Get()), + ExtractTxPower(advertisement.Get()), + ExtractServiceData(advertisement.Get()), + ExtractManufacturerData(advertisement.Get())); } } // namespace @@ -207,11 +482,15 @@ bool BluetoothAdapterWinrt::IsPresent() const { return adapter_ != nullptr; } +bool BluetoothAdapterWinrt::CanPower() const { + return radio_ != nullptr; +} + bool BluetoothAdapterWinrt::IsPowered() const { // Due to an issue on WoW64 we might fail to obtain the radio in OnGetRadio(). // This is why it can be null here. if (!radio_) - return false; + return num_powered_radios_ != 0; RadioState state; HRESULT hr = radio_->get_State(&state); @@ -237,8 +516,7 @@ void BluetoothAdapterWinrt::SetDiscoverable( } bool BluetoothAdapterWinrt::IsDiscovering() const { - NOTIMPLEMENTED(); - return false; + return num_discovery_sessions_ != 0; } BluetoothAdapter::UUIDList BluetoothAdapterWinrt::GetUUIDs() const { @@ -266,7 +544,37 @@ void BluetoothAdapterWinrt::RegisterAdvertisement( std::unique_ptr<BluetoothAdvertisement::Data> advertisement_data, const CreateAdvertisementCallback& callback, const AdvertisementErrorCallback& error_callback) { - NOTIMPLEMENTED(); + auto advertisement = CreateAdvertisement(); + if (!advertisement->Initialize(std::move(advertisement_data))) { + VLOG(2) << "Failed to Initialize Advertisement."; + ui_task_runner_->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothAdvertisement::ERROR_STARTING_ADVERTISEMENT)); + return; + } + + // In order to avoid |advertisement| holding a strong reference to itself, we + // pass only a weak reference to the callbacks, and store a strong reference + // in |pending_advertisements_|. When the callbacks are run, they will remove + // the corresponding advertisement from the list of pending advertisements. + advertisement->Register( + base::Bind(&BluetoothAdapterWinrt::OnRegisterAdvertisement, + weak_ptr_factory_.GetWeakPtr(), + base::Unretained(advertisement.get()), callback), + base::Bind(&BluetoothAdapterWinrt::OnRegisterAdvertisementError, + weak_ptr_factory_.GetWeakPtr(), + base::Unretained(advertisement.get()), error_callback)); + + pending_advertisements_.push_back(std::move(advertisement)); +} + +std::vector<BluetoothAdvertisement*> +BluetoothAdapterWinrt::GetPendingAdvertisementsForTesting() const { + std::vector<BluetoothAdvertisement*> pending_advertisements; + for (const auto& pending_advertisement : pending_advertisements_) + pending_advertisements.push_back(pending_advertisement.get()); + return pending_advertisements; } BluetoothLocalGattService* BluetoothAdapterWinrt::GetGattService( @@ -275,11 +583,38 @@ BluetoothLocalGattService* BluetoothAdapterWinrt::GetGattService( return nullptr; } +IRadio* BluetoothAdapterWinrt::GetRadioForTesting() { + return radio_.Get(); +} + +IDeviceWatcher* BluetoothAdapterWinrt::GetPoweredRadioWatcherForTesting() { + return powered_radio_watcher_.Get(); +} + BluetoothAdapterWinrt::BluetoothAdapterWinrt() : weak_ptr_factory_(this) { ui_task_runner_ = base::ThreadTaskRunnerHandle::Get(); } -BluetoothAdapterWinrt::~BluetoothAdapterWinrt() = default; +BluetoothAdapterWinrt::~BluetoothAdapterWinrt() { + // Explicitly move |pending_advertisements_| into a local variable and clear + // them out. Any remaining pending advertisement will attempt to remove itself + // from |pending_advertisements_|, which would result in a double-free + // otherwise. + auto pending_advertisements = std::move(pending_advertisements_); + pending_advertisements_.clear(); + + if (radio_) + TryRemoveRadioStateChangedHandler(); + + if (powered_radio_watcher_) { + TryRemovePoweredRadioEventHandlers(); + HRESULT hr = powered_radio_watcher_->Stop(); + if (FAILED(hr)) { + VLOG(2) << "Stopping powered radio watcher failed: " + << logging::SystemErrorCodeToString(hr); + } + } +} void BluetoothAdapterWinrt::Init(InitCallback init_cb) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); @@ -341,7 +676,7 @@ bool BluetoothAdapterWinrt::SetPoweredImpl(bool powered) { } hr = PostAsyncResults(std::move(set_state_op), - base::BindOnce(&BluetoothAdapterWinrt::OnSetState, + base::BindOnce(&BluetoothAdapterWinrt::OnSetRadioState, weak_ptr_factory_.GetWeakPtr())); if (FAILED(hr)) { @@ -530,6 +865,11 @@ BluetoothAdapterWinrt::ActivateBluetoothAdvertisementLEWatcherInstance( return watcher.CopyTo(instance); } +scoped_refptr<BluetoothAdvertisementWinrt> +BluetoothAdapterWinrt::CreateAdvertisement() const { + return base::MakeRefCounted<BluetoothAdvertisementWinrt>(); +} + std::unique_ptr<BluetoothDeviceWinrt> BluetoothAdapterWinrt::CreateDevice( uint64_t raw_address, base::Optional<std::string> local_name) { @@ -631,7 +971,7 @@ void BluetoothAdapterWinrt::OnCreateFromIdAsync( hr = PostAsyncResults( std::move(request_access_op), - base::BindOnce(&BluetoothAdapterWinrt::OnRequestAccess, + base::BindOnce(&BluetoothAdapterWinrt::OnRequestRadioAccess, weak_ptr_factory_.GetWeakPtr(), std::move(on_init))); if (FAILED(hr)) { @@ -640,8 +980,9 @@ void BluetoothAdapterWinrt::OnCreateFromIdAsync( } } -void BluetoothAdapterWinrt::OnRequestAccess(base::ScopedClosureRunner on_init, - RadioAccessStatus access_status) { +void BluetoothAdapterWinrt::OnRequestRadioAccess( + base::ScopedClosureRunner on_init, + RadioAccessStatus access_status) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); if (access_status != RadioAccessStatus_Allowed) { VLOG(2) << "Got unexpected Radio Access Status: " @@ -670,25 +1011,117 @@ void BluetoothAdapterWinrt::OnRequestAccess(base::ScopedClosureRunner on_init, void BluetoothAdapterWinrt::OnGetRadio(base::ScopedClosureRunner on_init, ComPtr<IRadio> radio) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); - if (!radio) { - // This happens within WoW64, due to an issue with non-native APIs. - VLOG(2) << "Getting Radio failed."; + if (radio) { + radio_ = std::move(radio); + radio_state_changed_token_ = AddTypedEventHandler( + radio_.Get(), &IRadio::add_StateChanged, + base::BindRepeating(&BluetoothAdapterWinrt::OnRadioStateChanged, + weak_ptr_factory_.GetWeakPtr())); + + if (!radio_state_changed_token_) + VLOG(2) << "Adding Radio State Changed Handler failed."; + return; + } + + // This happens within WoW64, due to an issue with non-native APIs. + VLOG(2) << "Getting Radio failed. Chrome will be unable to change the power " + "state by itself."; + + // Attempt to create a DeviceWatcher for powered radios, so that querying + // the power state is still possible. + ComPtr<IDeviceInformationStatics> device_information_statics; + HRESULT hr = + GetDeviceInformationStaticsActivationFactory(&device_information_statics); + if (FAILED(hr)) { + VLOG(2) << "GetDeviceInformationStaticsActivationFactory failed: " + << logging::SystemErrorCodeToString(hr); return; } - radio_ = std::move(radio); + auto aqs_filter = base::win::ScopedHString::Create(kPoweredRadiosAqsFilter); + hr = device_information_statics->CreateWatcherAqsFilter( + aqs_filter.get(), &powered_radio_watcher_); + if (FAILED(hr)) { + VLOG(2) << "Creating Powered Radios Watcher failed: " + << logging::SystemErrorCodeToString(hr); + return; + } + + powered_radio_added_token_ = AddTypedEventHandler( + powered_radio_watcher_.Get(), &IDeviceWatcher::add_Added, + base::BindRepeating(&BluetoothAdapterWinrt::OnPoweredRadioAdded, + weak_ptr_factory_.GetWeakPtr())); + + powered_radio_removed_token_ = AddTypedEventHandler( + powered_radio_watcher_.Get(), &IDeviceWatcher::add_Removed, + base::BindRepeating(&BluetoothAdapterWinrt::OnPoweredRadioRemoved, + weak_ptr_factory_.GetWeakPtr())); + + powered_radios_enumerated_token_ = AddTypedEventHandler( + powered_radio_watcher_.Get(), &IDeviceWatcher::add_EnumerationCompleted, + base::BindRepeating(&BluetoothAdapterWinrt::OnPoweredRadiosEnumerated, + weak_ptr_factory_.GetWeakPtr())); + + if (!powered_radio_added_token_ || !powered_radio_removed_token_ || + !powered_radios_enumerated_token_) { + VLOG(2) << "Failed to Register Powered Radio Event Handlers."; + TryRemovePoweredRadioEventHandlers(); + return; + } + + hr = powered_radio_watcher_->Start(); + if (FAILED(hr)) { + VLOG(2) << "Starting the Powered Radio Watcher failed: " + << logging::SystemErrorCodeToString(hr); + TryRemovePoweredRadioEventHandlers(); + return; + } + + // Store the Closure Runner. It is expected that OnPoweredRadiosEnumerated() + // is invoked soon after. + on_init_ = std::make_unique<base::ScopedClosureRunner>(std::move(on_init)); } -void BluetoothAdapterWinrt::OnSetState(RadioAccessStatus access_status) { +void BluetoothAdapterWinrt::OnSetRadioState(RadioAccessStatus access_status) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); if (access_status != RadioAccessStatus_Allowed) { VLOG(2) << "Got unexpected Radio Access Status: " << ToCString(access_status); - } else { - NotifyAdapterPoweredChanged(IsPowered()); + RunPendingPowerCallbacks(); } +} - DidChangePoweredState(); +void BluetoothAdapterWinrt::OnRadioStateChanged(IRadio* radio, + IInspectable* object) { + RunPendingPowerCallbacks(); + NotifyAdapterPoweredChanged(IsPowered()); +} + +void BluetoothAdapterWinrt::OnPoweredRadioAdded(IDeviceWatcher* watcher, + IDeviceInformation* info) { + if (++num_powered_radios_ == 1) + NotifyAdapterPoweredChanged(true); + VLOG(2) << "OnPoweredRadioAdded(), Number of Powered Radios: " + << num_powered_radios_; +} + +void BluetoothAdapterWinrt::OnPoweredRadioRemoved( + IDeviceWatcher* watcher, + IDeviceInformationUpdate* update) { + if (--num_powered_radios_ == 0) + NotifyAdapterPoweredChanged(false); + VLOG(2) << "OnPoweredRadioRemoved(), Number of Powered Radios: " + << num_powered_radios_; +} + +void BluetoothAdapterWinrt::OnPoweredRadiosEnumerated(IDeviceWatcher* watcher, + IInspectable* object) { + // Destroy the ScopedClosureRunner, triggering the contained Closure to be + // run. + DCHECK(on_init_); + on_init_.reset(); + VLOG(2) << "OnPoweredRadiosEnumerated(), Number of Powered Radios: " + << num_powered_radios_; } void BluetoothAdapterWinrt::OnAdvertisementReceived( @@ -725,6 +1158,75 @@ void BluetoothAdapterWinrt::OnAdvertisementReceived( } } +void BluetoothAdapterWinrt::OnRegisterAdvertisement( + BluetoothAdvertisement* advertisement, + const CreateAdvertisementCallback& callback) { + DCHECK(base::ContainsValue(pending_advertisements_, advertisement)); + auto wrapped_advertisement = base::WrapRefCounted(advertisement); + base::Erase(pending_advertisements_, advertisement); + callback.Run(std::move(wrapped_advertisement)); +} + +void BluetoothAdapterWinrt::OnRegisterAdvertisementError( + BluetoothAdvertisement* advertisement, + const AdvertisementErrorCallback& error_callback, + BluetoothAdvertisement::ErrorCode error_code) { + // Note: We are not DCHECKing that |pending_advertisements_| contains + // |advertisement|, as this method might be invoked during destruction. + base::Erase(pending_advertisements_, advertisement); + error_callback.Run(error_code); +} + +void BluetoothAdapterWinrt::TryRemoveRadioStateChangedHandler() { + DCHECK(radio_); + if (!radio_state_changed_token_) + return; + + HRESULT hr = radio_->remove_StateChanged(*radio_state_changed_token_); + if (FAILED(hr)) { + VLOG(2) << "Removing Radio State Changed Handler failed: " + << logging::SystemErrorCodeToString(hr); + } + + radio_state_changed_token_.reset(); +} + +void BluetoothAdapterWinrt::TryRemovePoweredRadioEventHandlers() { + DCHECK(powered_radio_watcher_); + if (powered_radio_added_token_) { + HRESULT hr = + powered_radio_watcher_->remove_Added(*powered_radio_removed_token_); + if (FAILED(hr)) { + VLOG(2) << "Removing the Powered Radio Added Handler failed: " + << logging::SystemErrorCodeToString(hr); + } + + powered_radio_added_token_.reset(); + } + + if (powered_radio_removed_token_) { + HRESULT hr = + powered_radio_watcher_->remove_Removed(*powered_radio_removed_token_); + if (FAILED(hr)) { + VLOG(2) << "Removing the Powered Radio Removed Handler failed: " + << logging::SystemErrorCodeToString(hr); + } + + powered_radio_removed_token_.reset(); + } + + if (powered_radios_enumerated_token_) { + HRESULT hr = powered_radio_watcher_->remove_EnumerationCompleted( + *powered_radios_enumerated_token_); + if (FAILED(hr)) { + VLOG(2) << "Removing the Powered Radios Enumerated Handler failed: " + << logging::SystemErrorCodeToString(hr); + } + + powered_radios_enumerated_token_.reset(); + } +} + void BluetoothAdapterWinrt::RemoveAdvertisementReceivedHandler() { DCHECK(ble_advertisement_watcher_); HRESULT hr = ble_advertisement_watcher_->remove_Received( diff --git a/chromium/device/bluetooth/bluetooth_adapter_winrt.h b/chromium/device/bluetooth/bluetooth_adapter_winrt.h index cb60c4c84f1..25611d511da 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_winrt.h +++ b/chromium/device/bluetooth/bluetooth_adapter_winrt.h @@ -12,9 +12,11 @@ #include <memory> #include <string> +#include <vector> #include "base/callback_forward.h" #include "base/macros.h" +#include "base/memory/ref_counted.h" #include "base/threading/thread_checker.h" #include "device/bluetooth/bluetooth_adapter.h" #include "device/bluetooth/bluetooth_export.h" @@ -25,6 +27,7 @@ class ScopedClosureRunner; namespace device { +class BluetoothAdvertisementWinrt; class BluetoothDeviceWinrt; class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterWinrt : public BluetoothAdapter { @@ -37,6 +40,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterWinrt : public BluetoothAdapter { const ErrorCallback& error_callback) override; bool IsInitialized() const override; bool IsPresent() const override; + bool CanPower() const override; bool IsPowered() const override; bool IsDiscoverable() const override; void SetDiscoverable(bool discoverable, @@ -58,9 +62,15 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterWinrt : public BluetoothAdapter { std::unique_ptr<BluetoothAdvertisement::Data> advertisement_data, const CreateAdvertisementCallback& callback, const AdvertisementErrorCallback& error_callback) override; + std::vector<BluetoothAdvertisement*> GetPendingAdvertisementsForTesting() + const override; BluetoothLocalGattService* GetGattService( const std::string& identifier) const override; + ABI::Windows::Devices::Radios::IRadio* GetRadioForTesting(); + ABI::Windows::Devices::Enumeration::IDeviceWatcher* + GetPoweredRadioWatcherForTesting(); + protected: friend class BluetoothAdapterWin; friend class BluetoothTestWinrt; @@ -102,6 +112,10 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterWinrt : public BluetoothAdapter { virtual HRESULT ActivateBluetoothAdvertisementLEWatcherInstance( ABI::Windows::Devices::Bluetooth::Advertisement:: IBluetoothLEAdvertisementWatcher** instance) const; + + virtual scoped_refptr<BluetoothAdvertisementWinrt> CreateAdvertisement() + const; + virtual std::unique_ptr<BluetoothDeviceWinrt> CreateDevice( uint64_t raw_address, base::Optional<std::string> local_name); @@ -118,7 +132,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterWinrt : public BluetoothAdapter { ABI::Windows::Devices::Enumeration::IDeviceInformation> device_information); - void OnRequestAccess( + void OnRequestRadioAccess( base::ScopedClosureRunner on_init, ABI::Windows::Devices::Radios::RadioAccessStatus access_status); @@ -126,24 +140,63 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterWinrt : public BluetoothAdapter { base::ScopedClosureRunner on_init, Microsoft::WRL::ComPtr<ABI::Windows::Devices::Radios::IRadio> radio); - void OnSetState( + void OnSetRadioState( ABI::Windows::Devices::Radios::RadioAccessStatus access_status); + void OnRadioStateChanged(ABI::Windows::Devices::Radios::IRadio* radio, + IInspectable* object); + + void OnPoweredRadioAdded( + ABI::Windows::Devices::Enumeration::IDeviceWatcher* watcher, + ABI::Windows::Devices::Enumeration::IDeviceInformation* info); + + void OnPoweredRadioRemoved( + ABI::Windows::Devices::Enumeration::IDeviceWatcher* watcher, + ABI::Windows::Devices::Enumeration::IDeviceInformationUpdate* update); + + void OnPoweredRadiosEnumerated( + ABI::Windows::Devices::Enumeration::IDeviceWatcher* watcher, + IInspectable* object); + void OnAdvertisementReceived( ABI::Windows::Devices::Bluetooth::Advertisement:: IBluetoothLEAdvertisementWatcher* watcher, ABI::Windows::Devices::Bluetooth::Advertisement:: IBluetoothLEAdvertisementReceivedEventArgs* received); + void OnRegisterAdvertisement(BluetoothAdvertisement* advertisement, + const CreateAdvertisementCallback& callback); + + void OnRegisterAdvertisementError( + BluetoothAdvertisement* advertisement, + const AdvertisementErrorCallback& error_callback, + BluetoothAdvertisement::ErrorCode error_code); + + void TryRemoveRadioStateChangedHandler(); + + void TryRemovePoweredRadioEventHandlers(); + void RemoveAdvertisementReceivedHandler(); bool is_initialized_ = false; std::string address_; std::string name_; + std::unique_ptr<base::ScopedClosureRunner> on_init_; Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::IBluetoothAdapter> adapter_; + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Radios::IRadio> radio_; + base::Optional<EventRegistrationToken> radio_state_changed_token_; + + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Enumeration::IDeviceWatcher> + powered_radio_watcher_; + base::Optional<EventRegistrationToken> powered_radio_added_token_; + base::Optional<EventRegistrationToken> powered_radio_removed_token_; + base::Optional<EventRegistrationToken> powered_radios_enumerated_token_; + size_t num_powered_radios_ = 0; + + std::vector<scoped_refptr<BluetoothAdvertisement>> pending_advertisements_; size_t num_discovery_sessions_ = 0; EventRegistrationToken advertisement_received_token_; diff --git a/chromium/device/bluetooth/bluetooth_advertisement.h b/chromium/device/bluetooth/bluetooth_advertisement.h index f17bb025bfd..041738f7ea8 100644 --- a/chromium/device/bluetooth/bluetooth_advertisement.h +++ b/chromium/device/bluetooth/bluetooth_advertisement.h @@ -146,7 +146,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdvertisement // List of observers interested in event notifications from us. Objects in // |observers_| are expected to outlive a BluetoothAdvertisement object. - base::ObserverList<BluetoothAdvertisement::Observer> observers_; + base::ObserverList<BluetoothAdvertisement::Observer>::Unchecked observers_; private: DISALLOW_COPY_AND_ASSIGN(BluetoothAdvertisement); diff --git a/chromium/device/bluetooth/bluetooth_advertisement_winrt.cc b/chromium/device/bluetooth/bluetooth_advertisement_winrt.cc new file mode 100644 index 00000000000..71f603702b2 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_advertisement_winrt.cc @@ -0,0 +1,416 @@ +// Copyright 2018 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 "device/bluetooth/bluetooth_advertisement_winrt.h" + +#include <windows.foundation.collections.h> +#include <windows.storage.streams.h> + +#include <utility> +#include <vector> + +#include "base/bind_helpers.h" +#include "base/logging.h" +#include "base/strings/string_piece.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/win/core_winrt_util.h" +#include "base/win/scoped_hstring.h" +#include "base/win/winrt_storage_util.h" +#include "device/bluetooth/event_utils_winrt.h" + +namespace device { + +namespace { + +using ABI::Windows::Devices::Bluetooth::Advertisement:: + BluetoothLEAdvertisementPublisherStatus; +using ABI::Windows::Devices::Bluetooth::Advertisement:: + BluetoothLEAdvertisementPublisherStatus_Aborted; +using ABI::Windows::Devices::Bluetooth::Advertisement:: + BluetoothLEAdvertisementPublisherStatus_Started; +using ABI::Windows::Devices::Bluetooth::Advertisement:: + BluetoothLEAdvertisementPublisherStatus_Stopped; +using ABI::Windows::Devices::Bluetooth::Advertisement:: + BluetoothLEManufacturerData; +using ABI::Windows::Devices::Bluetooth::Advertisement:: + IBluetoothLEAdvertisement; +using ABI::Windows::Devices::Bluetooth::Advertisement:: + IBluetoothLEAdvertisementPublisher; +using ABI::Windows::Devices::Bluetooth::Advertisement:: + IBluetoothLEAdvertisementPublisherFactory; +using ABI::Windows::Devices::Bluetooth::Advertisement:: + IBluetoothLEAdvertisementPublisherStatusChangedEventArgs; +using ABI::Windows::Devices::Bluetooth::Advertisement:: + IBluetoothLEManufacturerData; +using ABI::Windows::Devices::Bluetooth::Advertisement:: + IBluetoothLEManufacturerDataFactory; +using ABI::Windows::Devices::Bluetooth::BluetoothError; +using ABI::Windows::Devices::Bluetooth::BluetoothError_NotSupported; +using ABI::Windows::Devices::Bluetooth::BluetoothError_RadioNotAvailable; +using ABI::Windows::Foundation::Collections::IVector; +using ABI::Windows::Storage::Streams::IBuffer; +using Microsoft::WRL::ComPtr; + +void RemoveStatusChangedHandler(IBluetoothLEAdvertisementPublisher* publisher, + EventRegistrationToken token) { + HRESULT hr = publisher->remove_StatusChanged(token); + if (FAILED(hr)) { + VLOG(2) << "Removing StatusChanged Handler failed: " + << logging::SystemErrorCodeToString(hr); + } +} + +} // namespace + +BluetoothAdvertisementWinrt::BluetoothAdvertisementWinrt() + : weak_ptr_factory_(this) {} + +bool BluetoothAdvertisementWinrt::Initialize( + std::unique_ptr<BluetoothAdvertisement::Data> advertisement_data) { + if (advertisement_data->service_uuids()) { + VLOG(2) << "Windows does not support advertising Service UUIDs."; + return false; + } + + if (advertisement_data->solicit_uuids()) { + VLOG(2) << "Windows does not support advertising Solicit UUIDs."; + return false; + } + + if (advertisement_data->service_data()) { + VLOG(2) << "Windows does not support advertising Service Data."; + return false; + } + + auto manufacturer_data = advertisement_data->manufacturer_data(); + if (!manufacturer_data) { + VLOG(2) << "No Manufacturer Data present."; + return false; + } + + ComPtr<IBluetoothLEAdvertisement> advertisement; + HRESULT hr = ActivateBluetoothLEAdvertisementInstance(&advertisement); + if (FAILED(hr)) { + VLOG(2) << "ActivateBluetoothLEAdvertisementInstance failed: " + << logging::SystemErrorCodeToString(hr); + return false; + } + + ComPtr<IVector<BluetoothLEManufacturerData*>> manufacturer_data_list; + hr = advertisement->get_ManufacturerData(&manufacturer_data_list); + if (FAILED(hr)) { + VLOG(2) << "Getting ManufacturerData failed: " + << logging::SystemErrorCodeToString(hr); + return false; + } + + ComPtr<IBluetoothLEManufacturerDataFactory> manufacturer_data_factory; + hr = GetBluetoothLEManufacturerDataFactory(&manufacturer_data_factory); + if (FAILED(hr)) { + VLOG(2) << "GetBluetoothLEManufacturerDataFactory failed: " + << logging::SystemErrorCodeToString(hr); + return false; + } + + for (const auto& pair : *manufacturer_data) { + uint16_t manufacturer = pair.first; + const std::vector<uint8_t>& data = pair.second; + + ComPtr<IBuffer> buffer; + hr = base::win::CreateIBufferFromData(data.data(), data.size(), &buffer); + if (FAILED(hr)) { + VLOG(2) << "CreateIBufferFromData() failed: " + << logging::SystemErrorCodeToString(hr); + return false; + } + + ComPtr<IBluetoothLEManufacturerData> manufacturer_data_entry; + hr = manufacturer_data_factory->Create(manufacturer, buffer.Get(), + &manufacturer_data_entry); + if (FAILED(hr)) { + VLOG(2) << "Creating BluetoothLEManufacturerData failed: " + << logging::SystemErrorCodeToString(hr); + return false; + } + + hr = manufacturer_data_list->Append(manufacturer_data_entry.Get()); + if (FAILED(hr)) { + VLOG(2) << "Appending BluetoothLEManufacturerData failed: " + << logging::SystemErrorCodeToString(hr); + return false; + } + } + + ComPtr<IBluetoothLEAdvertisementPublisherFactory> publisher_factory; + hr = + GetBluetoothLEAdvertisementPublisherActivationFactory(&publisher_factory); + if (FAILED(hr)) { + VLOG(2) << "GetBluetoothLEAdvertisementPublisherActivationFactory " + "failed:" + << logging::SystemErrorCodeToString(hr); + return false; + } + + hr = publisher_factory->Create(advertisement.Get(), &publisher_); + if (FAILED(hr)) { + VLOG(2) << "Creating IBluetoothLEAdvertisementPublisher failed: " + << logging::SystemErrorCodeToString(hr); + return false; + } + + return true; +} + +void BluetoothAdvertisementWinrt::Register(SuccessCallback callback, + ErrorCallback error_callback) { + // Register should only be called once during initialization. + DCHECK(!status_changed_token_); + DCHECK(!pending_register_callbacks_); + DCHECK(!pending_unregister_callbacks_); + + // Register should only be called after successful initialization. + DCHECK(publisher_); + + status_changed_token_ = AddTypedEventHandler( + publisher_.Get(), &IBluetoothLEAdvertisementPublisher::add_StatusChanged, + base::BindRepeating(&BluetoothAdvertisementWinrt::OnStatusChanged, + weak_ptr_factory_.GetWeakPtr())); + if (!status_changed_token_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(error_callback), + ERROR_STARTING_ADVERTISEMENT)); + return; + } + + HRESULT hr = publisher_->Start(); + if (FAILED(hr)) { + VLOG(2) << "Starting IBluetoothLEAdvertisementPublisher failed: " + << logging::SystemErrorCodeToString(hr); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(error_callback), + ERROR_STARTING_ADVERTISEMENT)); + RemoveStatusChangedHandler(publisher_.Get(), *status_changed_token_); + status_changed_token_.reset(); + return; + } + + pending_register_callbacks_ = std::make_unique<PendingCallbacks>( + std::move(callback), std::move(error_callback)); +} + +void BluetoothAdvertisementWinrt::Unregister( + const SuccessCallback& success_callback, + const ErrorCallback& error_callback) { + // Unregister() should only be called when an advertisement is registered + // already, or during destruction. In both of these cases there should be no + // pending register callbacks and the publisher should be present. + DCHECK(!pending_register_callbacks_); + DCHECK(publisher_); + + if (pending_unregister_callbacks_) { + VLOG(2) << "An Unregister Operation is already in progress."; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(error_callback, ERROR_RESET_ADVERTISING)); + return; + } + + BluetoothLEAdvertisementPublisherStatus status; + HRESULT hr = publisher_->get_Status(&status); + if (FAILED(hr)) { + VLOG(2) << "Getting the Publisher Status failed: " + << logging::SystemErrorCodeToString(hr); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(error_callback, ERROR_RESET_ADVERTISING)); + return; + } + + if (status == BluetoothLEAdvertisementPublisherStatus_Aborted) { + // Report an error if the publisher is in the aborted state. + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(error_callback, ERROR_RESET_ADVERTISING)); + return; + } + + if (status == BluetoothLEAdvertisementPublisherStatus_Stopped) { + // Report success if the publisher is already stopped. + base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, success_callback); + return; + } + + hr = publisher_->Stop(); + if (FAILED(hr)) { + VLOG(2) << "IBluetoothLEAdvertisementPublisher::Stop() failed: " + << logging::SystemErrorCodeToString(hr); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(error_callback, ERROR_RESET_ADVERTISING)); + return; + } + + pending_unregister_callbacks_ = + std::make_unique<PendingCallbacks>(success_callback, error_callback); +} + +IBluetoothLEAdvertisementPublisher* +BluetoothAdvertisementWinrt::GetPublisherForTesting() { + return publisher_.Get(); +} + +BluetoothAdvertisementWinrt::~BluetoothAdvertisementWinrt() { + if (status_changed_token_) { + DCHECK(publisher_); + RemoveStatusChangedHandler(publisher_.Get(), *status_changed_token_); + } + + // Stop any pending register operation. + if (pending_register_callbacks_) { + auto callbacks = std::move(pending_register_callbacks_); + std::move(callbacks->error_callback).Run(ERROR_STARTING_ADVERTISEMENT); + } + + // Unregister the advertisement on a best effort basis if it's not already in + // process of doing so. + if (!pending_unregister_callbacks_ && publisher_) + Unregister(base::DoNothing(), base::DoNothing()); +} + +HRESULT +BluetoothAdvertisementWinrt:: + GetBluetoothLEAdvertisementPublisherActivationFactory( + IBluetoothLEAdvertisementPublisherFactory** factory) const { + return base::win::GetActivationFactory< + IBluetoothLEAdvertisementPublisherFactory, + RuntimeClass_Windows_Devices_Bluetooth_Advertisement_BluetoothLEAdvertisementPublisher>( + factory); +} + +HRESULT +BluetoothAdvertisementWinrt::ActivateBluetoothLEAdvertisementInstance( + IBluetoothLEAdvertisement** instance) const { + auto advertisement_hstring = base::win::ScopedHString::Create( + RuntimeClass_Windows_Devices_Bluetooth_Advertisement_BluetoothLEAdvertisement); + if (!advertisement_hstring.is_valid()) + return E_FAIL; + + ComPtr<IInspectable> inspectable; + HRESULT hr = + base::win::RoActivateInstance(advertisement_hstring.get(), &inspectable); + if (FAILED(hr)) { + VLOG(2) << "RoActivateInstance failed: " + << logging::SystemErrorCodeToString(hr); + return hr; + } + + ComPtr<IBluetoothLEAdvertisement> advertisement; + hr = inspectable.As(&advertisement); + if (FAILED(hr)) { + VLOG(2) << "As IBluetoothLEAdvertisementWatcher failed: " + << logging::SystemErrorCodeToString(hr); + return hr; + } + + return advertisement.CopyTo(instance); +} + +HRESULT +BluetoothAdvertisementWinrt::GetBluetoothLEManufacturerDataFactory( + IBluetoothLEManufacturerDataFactory** factory) const { + return base::win::GetActivationFactory< + IBluetoothLEManufacturerDataFactory, + RuntimeClass_Windows_Devices_Bluetooth_Advertisement_BluetoothLEManufacturerData>( + factory); +} + +BluetoothAdvertisementWinrt::PendingCallbacks::PendingCallbacks( + SuccessCallback callback, + ErrorCallback error_callback) + : callback(std::move(callback)), + error_callback(std::move(error_callback)) {} + +BluetoothAdvertisementWinrt::PendingCallbacks::~PendingCallbacks() = default; + +void BluetoothAdvertisementWinrt::OnStatusChanged( + IBluetoothLEAdvertisementPublisher* publisher, + IBluetoothLEAdvertisementPublisherStatusChangedEventArgs* changed) { + BluetoothLEAdvertisementPublisherStatus status; + HRESULT hr = changed->get_Status(&status); + if (FAILED(hr)) { + VLOG(2) << "Getting the Publisher Status failed: " + << logging::SystemErrorCodeToString(hr); + return; + } + + VLOG(2) << "Publisher Status: " << static_cast<int>(status); + if (status == BluetoothLEAdvertisementPublisherStatus_Stopped) { + // Notify Observers. + for (auto& observer : observers_) + observer.AdvertisementReleased(this); + } + + // Return early if there is no pending action. + if (!pending_register_callbacks_ && !pending_unregister_callbacks_) + return; + + // Register and Unregister should never be pending at the same time. + DCHECK(!pending_register_callbacks_ || !pending_unregister_callbacks_); + + const bool is_starting = pending_register_callbacks_ != nullptr; + ErrorCode error_code = + is_starting ? ERROR_STARTING_ADVERTISEMENT : ERROR_RESET_ADVERTISING; + + // Clears out pending callbacks by moving them into a local variable and runs + // the appropriate error callback with |error_code|. + auto run_error_cb = [&](ErrorCode error_code) { + auto callbacks = std::move(is_starting ? pending_register_callbacks_ + : pending_unregister_callbacks_); + std::move(callbacks->error_callback).Run(error_code); + }; + + if (status == BluetoothLEAdvertisementPublisherStatus_Aborted) { + VLOG(2) << "The Publisher aborted."; + BluetoothError bluetooth_error; + hr = changed->get_Error(&bluetooth_error); + if (FAILED(hr)) { + VLOG(2) << "Getting the Publisher Error failed: " + << logging::SystemErrorCodeToString(hr); + run_error_cb(error_code); + return; + } + + VLOG(2) << "Publisher Error: " << static_cast<int>(bluetooth_error); + switch (bluetooth_error) { + case BluetoothError_RadioNotAvailable: + error_code = ERROR_ADAPTER_POWERED_OFF; + break; + case BluetoothError_NotSupported: + error_code = ERROR_UNSUPPORTED_PLATFORM; + break; + default: + break; + } + + run_error_cb(error_code); + return; + } + + if (is_starting && + status == BluetoothLEAdvertisementPublisherStatus_Started) { + VLOG(2) << "Starting the Publisher was successful."; + auto callbacks = std::move(pending_register_callbacks_); + std::move(callbacks->callback).Run(); + return; + } + + if (!is_starting && + status == BluetoothLEAdvertisementPublisherStatus_Stopped) { + VLOG(2) << "Stopping the Publisher was successful."; + auto callbacks = std::move(pending_unregister_callbacks_); + std::move(callbacks->callback).Run(); + return; + } + + // The other states are temporary and we expect a future StatusChanged + // event. +} + +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_advertisement_winrt.h b/chromium/device/bluetooth/bluetooth_advertisement_winrt.h new file mode 100644 index 00000000000..7681e5f6d71 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_advertisement_winrt.h @@ -0,0 +1,84 @@ +// Copyright 2018 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. + +#ifndef DEVICE_BLUETOOTH_BLUETOOTH_ADVERTISEMENT_WINRT_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_ADVERTISEMENT_WINRT_H_ + +#include <windows.devices.bluetooth.advertisement.h> +#include <wrl/client.h> + +#include <memory> + +#include "base/macros.h" +#include "base/memory/scoped_refptr.h" +#include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "device/bluetooth/bluetooth_adapter.h" +#include "device/bluetooth/bluetooth_advertisement.h" +#include "device/bluetooth/bluetooth_export.h" + +namespace device { + +class DEVICE_BLUETOOTH_EXPORT BluetoothAdvertisementWinrt + : public BluetoothAdvertisement { + public: + BluetoothAdvertisementWinrt(); + bool Initialize( + std::unique_ptr<BluetoothAdvertisement::Data> advertisement_data); + void Register(SuccessCallback callback, ErrorCallback error_callback); + + // BluetoothAdvertisement: + void Unregister(const SuccessCallback& success_callback, + const ErrorCallback& error_callback) override; + + ABI::Windows::Devices::Bluetooth::Advertisement:: + IBluetoothLEAdvertisementPublisher* + GetPublisherForTesting(); + + protected: + ~BluetoothAdvertisementWinrt() override; + + // These are declared virtual so that they can be overridden by tests. + virtual HRESULT GetBluetoothLEAdvertisementPublisherActivationFactory( + ABI::Windows::Devices::Bluetooth::Advertisement:: + IBluetoothLEAdvertisementPublisherFactory** factory) const; + + virtual HRESULT ActivateBluetoothLEAdvertisementInstance( + ABI::Windows::Devices::Bluetooth::Advertisement:: + IBluetoothLEAdvertisement** instance) const; + + virtual HRESULT GetBluetoothLEManufacturerDataFactory( + ABI::Windows::Devices::Bluetooth::Advertisement:: + IBluetoothLEManufacturerDataFactory** factory) const; + + private: + struct PendingCallbacks { + PendingCallbacks(SuccessCallback callback, ErrorCallback error_callback); + ~PendingCallbacks(); + + SuccessCallback callback; + ErrorCallback error_callback; + }; + + void OnStatusChanged( + ABI::Windows::Devices::Bluetooth::Advertisement:: + IBluetoothLEAdvertisementPublisher* publisher, + ABI::Windows::Devices::Bluetooth::Advertisement:: + IBluetoothLEAdvertisementPublisherStatusChangedEventArgs* changed); + + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::Advertisement:: + IBluetoothLEAdvertisementPublisher> + publisher_; + base::Optional<EventRegistrationToken> status_changed_token_; + std::unique_ptr<PendingCallbacks> pending_register_callbacks_; + std::unique_ptr<PendingCallbacks> pending_unregister_callbacks_; + + base::WeakPtrFactory<BluetoothAdvertisementWinrt> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothAdvertisementWinrt); +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_ADVERTISEMENT_WINRT_H_ diff --git a/chromium/device/bluetooth/bluetooth_device.cc b/chromium/device/bluetooth/bluetooth_device.cc index 57134686533..dc280146a0c 100644 --- a/chromium/device/bluetooth/bluetooth_device.cc +++ b/chromium/device/bluetooth/bluetooth_device.cc @@ -10,6 +10,7 @@ #include <utility> #include "base/memory/ptr_util.h" +#include "base/stl_util.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/time/time.h" @@ -66,10 +67,8 @@ const BluetoothDevice::UUIDSet& BluetoothDevice::DeviceUUIDs::GetUUIDs() const { } void BluetoothDevice::DeviceUUIDs::UpdateDeviceUUIDs() { - device_uuids_.clear(); - std::set_union(advertised_uuids_.begin(), advertised_uuids_.end(), - service_uuids_.begin(), service_uuids_.end(), - std::inserter(device_uuids_, device_uuids_.begin())); + device_uuids_ = base::STLSetUnion<BluetoothDevice::UUIDSet>(advertised_uuids_, + service_uuids_); } BluetoothDevice::BluetoothDevice(BluetoothAdapter* adapter) diff --git a/chromium/device/bluetooth/bluetooth_device.h b/chromium/device/bluetooth/bluetooth_device.h index e0044d638a4..87ec108a0be 100644 --- a/chromium/device/bluetooth/bluetooth_device.h +++ b/chromium/device/bluetooth/bluetooth_device.h @@ -16,6 +16,7 @@ #include <vector> #include "base/callback.h" +#include "base/containers/flat_set.h" #include "base/gtest_prod_util.h" #include "base/macros.h" #include "base/memory/ref_counted.h" @@ -95,7 +96,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDevice { }; typedef std::vector<BluetoothUUID> UUIDList; - typedef std::unordered_set<BluetoothUUID, BluetoothUUIDHash> UUIDSet; + typedef base::flat_set<BluetoothUUID> UUIDSet; typedef std::unordered_map<BluetoothUUID, std::vector<uint8_t>, BluetoothUUIDHash> @@ -356,8 +357,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDevice { // Returns Advertising Data Flags. // Returns cached value if the adapter is not discovering. // - // TODO(crbug.com/661814) Support this on platforms that don't use BlueZ. - // Only Chrome OS supports this now. Upstream BlueZ has this feature + // Only Chrome OS and WinRT support this now. Upstream BlueZ has this feature // as experimental. This method returns base::nullopt on platforms that don't // support this feature. base::Optional<uint8_t> GetAdvertisingDataFlags() const; @@ -428,7 +428,8 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDevice { // ignores the |IsPaired()| value. // // In most cases |Connect()| should be preferred. This method is only - // implemented on ChromeOS and Linux. + // implemented on ChromeOS, Linux and Windows 10. On Windows, only pairing + // with a pin code is currently supported. virtual void Pair(PairingDelegate* pairing_delegate, const base::Closure& callback, const ConnectErrorCallback& error_callback); diff --git a/chromium/device/bluetooth/bluetooth_device_unittest.cc b/chromium/device/bluetooth/bluetooth_device_unittest.cc index 83924cb6713..a443aeffba4 100644 --- a/chromium/device/bluetooth/bluetooth_device_unittest.cc +++ b/chromium/device/bluetooth/bluetooth_device_unittest.cc @@ -13,6 +13,7 @@ #include "build/build_config.h" #include "device/bluetooth/bluetooth_remote_gatt_service.h" #include "device/bluetooth/test/test_bluetooth_adapter_observer.h" +#include "device/bluetooth/test/test_pairing_delegate.h" #include "testing/gtest/include/gtest/gtest.h" #if defined(OS_ANDROID) @@ -94,6 +95,171 @@ TEST(BluetoothDeviceTest, CanonicalizeAddressFormat_RejectsInvalidFormats) { } } +#if defined(OS_WIN) +TEST_P(BluetoothTestWinrtOnly, DeviceIsPaired) { + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + InitWithFakeAdapter(); + StartLowEnergyDiscoverySession(); + BluetoothDevice* device = SimulateLowEnergyDevice(1); + + // By default a device should not be paired. + EXPECT_FALSE(device->IsPaired()); + + // Connect to the device and simulate a paired state. + device->CreateGattConnection(GetGattConnectionCallback(Call::EXPECTED), + GetConnectErrorCallback(Call::NOT_EXPECTED)); + SimulateGattConnection(device); + base::RunLoop().RunUntilIdle(); + SimulateDevicePaired(device, true); + EXPECT_TRUE(device->IsPaired()); + + SimulateDevicePaired(device, false); + EXPECT_FALSE(device->IsPaired()); +} + +// Tests that providing a correct pin code results in a paired device. +TEST_P(BluetoothTestWinrtOnly, DevicePairRequestPinCodeCorrect) { + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + InitWithFakeAdapter(); + StartLowEnergyDiscoverySession(); + BluetoothDevice* device = SimulateLowEnergyDevice(1); + + device->CreateGattConnection(GetGattConnectionCallback(Call::EXPECTED), + GetConnectErrorCallback(Call::NOT_EXPECTED)); + SimulateGattConnection(device); + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(device->IsPaired()); + EXPECT_FALSE(device->ExpectingPinCode()); + + SimulatePairingPinCode(device, "123456"); + TestPairingDelegate pairing_delegate; + device->Pair(&pairing_delegate, GetCallback(Call::EXPECTED), + GetConnectErrorCallback(Call::NOT_EXPECTED)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.request_pincode_count_); + EXPECT_TRUE(device->ExpectingPinCode()); + + device->SetPinCode("123456"); + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(device->IsPaired()); + EXPECT_FALSE(device->ExpectingPinCode()); +} + +// Tests that providing a wrong pin code does not result in a paired device. +TEST_P(BluetoothTestWinrtOnly, DevicePairRequestPinCodeWrong) { + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + InitWithFakeAdapter(); + StartLowEnergyDiscoverySession(); + BluetoothDevice* device = SimulateLowEnergyDevice(1); + + device->CreateGattConnection(GetGattConnectionCallback(Call::EXPECTED), + GetConnectErrorCallback(Call::NOT_EXPECTED)); + SimulateGattConnection(device); + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(device->IsPaired()); + EXPECT_FALSE(device->ExpectingPinCode()); + + SimulatePairingPinCode(device, "123456"); + TestPairingDelegate pairing_delegate; + device->Pair(&pairing_delegate, GetCallback(Call::NOT_EXPECTED), + GetConnectErrorCallback(Call::EXPECTED)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.request_pincode_count_); + EXPECT_TRUE(device->ExpectingPinCode()); + + device->SetPinCode("000000"); + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(device->IsPaired()); + EXPECT_FALSE(device->ExpectingPinCode()); + EXPECT_EQ(BluetoothDevice::ERROR_FAILED, last_connect_error_code_); +} + +// Tests that rejecting the pairing does not result in a paired device. +TEST_P(BluetoothTestWinrtOnly, DevicePairRequestPinCodeRejectPairing) { + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + InitWithFakeAdapter(); + StartLowEnergyDiscoverySession(); + BluetoothDevice* device = SimulateLowEnergyDevice(1); + + device->CreateGattConnection(GetGattConnectionCallback(Call::EXPECTED), + GetConnectErrorCallback(Call::NOT_EXPECTED)); + SimulateGattConnection(device); + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(device->IsPaired()); + EXPECT_FALSE(device->ExpectingPinCode()); + + SimulatePairingPinCode(device, "123456"); + TestPairingDelegate pairing_delegate; + device->Pair(&pairing_delegate, GetCallback(Call::NOT_EXPECTED), + GetConnectErrorCallback(Call::EXPECTED)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.request_pincode_count_); + EXPECT_TRUE(device->ExpectingPinCode()); + + device->RejectPairing(); + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(device->IsPaired()); + EXPECT_FALSE(device->ExpectingPinCode()); + EXPECT_EQ(BluetoothDevice::ERROR_AUTH_REJECTED, last_connect_error_code_); +} + +// Tests that cancelling the pairing does not result in a paired device. +TEST_P(BluetoothTestWinrtOnly, DevicePairRequestPinCodeCancelPairing) { + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + InitWithFakeAdapter(); + StartLowEnergyDiscoverySession(); + BluetoothDevice* device = SimulateLowEnergyDevice(1); + + device->CreateGattConnection(GetGattConnectionCallback(Call::EXPECTED), + GetConnectErrorCallback(Call::NOT_EXPECTED)); + SimulateGattConnection(device); + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(device->IsPaired()); + EXPECT_FALSE(device->ExpectingPinCode()); + + SimulatePairingPinCode(device, "123456"); + TestPairingDelegate pairing_delegate; + device->Pair(&pairing_delegate, GetCallback(Call::NOT_EXPECTED), + GetConnectErrorCallback(Call::EXPECTED)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(1, pairing_delegate.call_count_); + EXPECT_EQ(1, pairing_delegate.request_pincode_count_); + EXPECT_TRUE(device->ExpectingPinCode()); + + device->CancelPairing(); + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(device->IsPaired()); + EXPECT_FALSE(device->ExpectingPinCode()); + EXPECT_EQ(BluetoothDevice::ERROR_AUTH_CANCELED, last_connect_error_code_); +} +#endif + // Verifies basic device properties, e.g. GetAddress, GetName, ... #if defined(OS_WIN) TEST_P(BluetoothTestWinrt, LowEnergyDeviceProperties) { @@ -151,7 +317,11 @@ TEST_F(BluetoothTest, LowEnergyDeviceNoUUIDs) { #define MAYBE_GetServiceDataUUIDs_GetServiceDataForUUID \ DISABLED_GetServiceDataUUIDs_GetServiceDataForUUID #endif +#if defined(OS_WIN) +TEST_P(BluetoothTestWinrtOnly, GetServiceDataUUIDs_GetServiceDataForUUID) { +#else TEST_F(BluetoothTest, MAYBE_GetServiceDataUUIDs_GetServiceDataForUUID) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -166,6 +336,7 @@ TEST_F(BluetoothTest, MAYBE_GetServiceDataUUIDs_GetServiceDataForUUID) { // Receive Advertisement with empty service data. BluetoothDevice* device1 = SimulateLowEnergyDevice(4); + EXPECT_FALSE(device1->GetAdvertisingDataFlags().has_value()); EXPECT_TRUE(device1->GetServiceData().empty()); EXPECT_TRUE(device1->GetServiceDataUUIDs().empty()); EXPECT_TRUE(device1->GetManufacturerData().empty()); @@ -173,6 +344,10 @@ TEST_F(BluetoothTest, MAYBE_GetServiceDataUUIDs_GetServiceDataForUUID) { // Receive Advertisement with service data. BluetoothDevice* device2 = SimulateLowEnergyDevice(1); +#if defined(OS_WIN) + EXPECT_TRUE(device2->GetAdvertisingDataFlags().has_value()); + EXPECT_EQ(0x04, device2->GetAdvertisingDataFlags().value()); +#endif EXPECT_EQ(ServiceDataMap({{BluetoothUUID(kTestUUIDHeartRate), {1}}}), device2->GetServiceData()); EXPECT_EQ(UUIDSet({BluetoothUUID(kTestUUIDHeartRate)}), @@ -181,7 +356,7 @@ TEST_F(BluetoothTest, MAYBE_GetServiceDataUUIDs_GetServiceDataForUUID) { *device2->GetServiceDataForUUID(BluetoothUUID(kTestUUIDHeartRate))); EXPECT_EQ(std::vector<uint8_t>({1, 2, 3, 4}), *device2->GetManufacturerDataForID(kTestManufacturerId)); - // Receive Advertisement with no service and manufacturer data. + // Receive Advertisement with no flags and no service and manufacturer data. SimulateLowEnergyDevice(3); // TODO(crbug.com/707039): Remove #if once the BlueZ caching behavior is @@ -198,6 +373,7 @@ TEST_F(BluetoothTest, MAYBE_GetServiceDataUUIDs_GetServiceDataForUUID) { EXPECT_EQ(std::vector<uint8_t>({1}), *device2->GetServiceDataForUUID(BluetoothUUID(kTestUUIDHeartRate))); #else + EXPECT_FALSE(device2->GetAdvertisingDataFlags().has_value()); EXPECT_TRUE(device2->GetServiceData().empty()); EXPECT_TRUE(device2->GetServiceDataUUIDs().empty()); EXPECT_TRUE(device2->GetManufacturerData().empty()); @@ -208,6 +384,10 @@ TEST_F(BluetoothTest, MAYBE_GetServiceDataUUIDs_GetServiceDataForUUID) { // Receive Advertisement with new service data and empty manufacturer data. SimulateLowEnergyDevice(2); +#if defined(OS_WIN) + EXPECT_TRUE(device2->GetAdvertisingDataFlags().has_value()); + EXPECT_EQ(0x05, device2->GetAdvertisingDataFlags().value()); +#endif EXPECT_EQ(ServiceDataMap( {{BluetoothUUID(kTestUUIDHeartRate), std::vector<uint8_t>({})}, {BluetoothUUID(kTestUUIDImmediateAlert), {0, 2}}}), @@ -229,9 +409,11 @@ TEST_F(BluetoothTest, MAYBE_GetServiceDataUUIDs_GetServiceDataForUUID) { // Stop discovery. discovery_sessions_[0]->Stop(GetCallback(Call::EXPECTED), GetErrorCallback(Call::NOT_EXPECTED)); + base::RunLoop().RunUntilIdle(); ASSERT_FALSE(adapter_->IsDiscovering()); ASSERT_FALSE(discovery_sessions_[0]->IsActive()); + EXPECT_FALSE(device2->GetAdvertisingDataFlags().has_value()); EXPECT_TRUE(device2->GetServiceData().empty()); EXPECT_TRUE(device2->GetServiceDataUUIDs().empty()); EXPECT_EQ(nullptr, @@ -248,7 +430,11 @@ TEST_F(BluetoothTest, MAYBE_GetServiceDataUUIDs_GetServiceDataForUUID) { #endif // Tests that the Advertisement Data fields are correctly updated during // discovery. +#if defined(OS_WIN) +TEST_P(BluetoothTestWinrtOnly, AdvertisementData_Discovery) { +#else TEST_F(BluetoothTest, MAYBE_AdvertisementData_Discovery) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -259,6 +445,7 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_Discovery) { // Start Discovery Session and receive Advertisement, should // not notify of device changed because the device is new. // - GetInquiryRSSI: Should return the packet's rssi. + // - GetAdvertisingDataFlags: Should return advertised flags. // - GetUUIDs: Should return Advertised UUIDs. // - GetServiceData: Should return advertised Service Data. // - GetInquiryTxPower: Should return the packet's advertised Tx Power. @@ -268,6 +455,10 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_Discovery) { EXPECT_EQ(0, observer.device_changed_count()); EXPECT_EQ(ToInt8(TestRSSI::LOWEST), device->GetInquiryRSSI().value()); +#if defined(OS_WIN) + EXPECT_TRUE(device->GetAdvertisingDataFlags().has_value()); + EXPECT_EQ(0x04, device->GetAdvertisingDataFlags().value()); +#endif EXPECT_EQ(UUIDSet({BluetoothUUID(kTestUUIDGenericAccess), BluetoothUUID(kTestUUIDGenericAttribute)}), device->GetUUIDs()); @@ -277,9 +468,10 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_Discovery) { device->GetManufacturerData()); EXPECT_EQ(ToInt8(TestTxPower::LOWEST), device->GetInquiryTxPower().value()); - // Receive Advertisement with no UUIDs, Service Data, or Tx Power, should - // notify device changed. + // Receive Advertisement with no flags, no UUIDs, Service Data, or Tx Power, + // should notify device changed. // - GetInquiryRSSI: Should return packet's rssi. + // - GetAdvertisingDataFlags: Should return nullopt because of no flags. // - GetUUIDs: Should return no UUIDs. // - GetServiceData: Should return empty map. // - GetInquiryTxPower: Should return nullopt because of no Tx Power. @@ -287,6 +479,7 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_Discovery) { EXPECT_EQ(1, observer.device_changed_count()); EXPECT_EQ(ToInt8(TestRSSI::LOW), device->GetInquiryRSSI().value()); + EXPECT_FALSE(device->GetAdvertisingDataFlags().has_value()); EXPECT_TRUE(device->GetUUIDs().empty()); EXPECT_TRUE(device->GetServiceData().empty()); EXPECT_TRUE(device->GetManufacturerData().empty()); @@ -295,6 +488,7 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_Discovery) { // Receive Advertisement with different UUIDs, Service Data, and Tx Power, // should notify device changed. // - GetInquiryRSSI: Should return last packet's rssi. + // - GetAdvertisingDataFlags: Should return last advertised flags. // - GetUUIDs: Should return latest Advertised UUIDs. // - GetServiceData: Should return last advertised Service Data. // - GetInquiryTxPower: Should return last advertised Tx Power. @@ -302,6 +496,10 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_Discovery) { EXPECT_EQ(2, observer.device_changed_count()); EXPECT_EQ(ToInt8(TestRSSI::LOWER), device->GetInquiryRSSI().value()); +#if defined(OS_WIN) + EXPECT_TRUE(device->GetAdvertisingDataFlags().has_value()); + EXPECT_EQ(0x05, device->GetAdvertisingDataFlags().value()); +#endif EXPECT_EQ(UUIDSet({BluetoothUUID(kTestUUIDImmediateAlert), BluetoothUUID(kTestUUIDLinkLoss)}), device->GetUUIDs()); @@ -317,6 +515,7 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_Discovery) { // Stop discovery session, should notify of device changed. // - GetInquiryRSSI: Should return nullopt because we are no longer // discovering. + // - GetAdvertisingDataFlags: Should return no flags. // - GetUUIDs: Should not return any UUIDs. // - GetServiceData: Should return empty map. // - GetMAnufacturerData: Should return empty map. @@ -324,12 +523,14 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_Discovery) { // discovering. discovery_sessions_[0]->Stop(GetCallback(Call::EXPECTED), GetErrorCallback(Call::NOT_EXPECTED)); + base::RunLoop().RunUntilIdle(); ASSERT_FALSE(adapter_->IsDiscovering()); ASSERT_FALSE(discovery_sessions_[0]->IsActive()); EXPECT_EQ(3, observer.device_changed_count()); EXPECT_FALSE(device->GetInquiryRSSI()); + EXPECT_FALSE(device->GetAdvertisingDataFlags().has_value()); EXPECT_TRUE(device->GetUUIDs().empty()); EXPECT_TRUE(device->GetServiceData().empty()); EXPECT_TRUE(device->GetManufacturerData().empty()); @@ -338,6 +539,7 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_Discovery) { // Discover the device again with different UUIDs, should notify of device // changed. // - GetInquiryRSSI: Should return last packet's rssi. + // - GetAdvertisingDataFlags: Should return last advertised flags. // - GetUUIDs: Should return only the latest Advertised UUIDs. // - GetServiceData: Should return last advertise Service Data. // - GetInquiryTxPower: Should return last advertised Tx Power. @@ -347,6 +549,10 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_Discovery) { EXPECT_EQ(4, observer.device_changed_count()); EXPECT_EQ(ToInt8(TestRSSI::LOWEST), device->GetInquiryRSSI().value()); +#if defined(OS_WIN) + EXPECT_TRUE(device->GetAdvertisingDataFlags().has_value()); + EXPECT_EQ(0x04, device->GetAdvertisingDataFlags().value()); +#endif EXPECT_EQ(UUIDSet({BluetoothUUID(kTestUUIDGenericAccess), BluetoothUUID(kTestUUIDGenericAttribute)}), device->GetUUIDs()); @@ -558,7 +764,11 @@ TEST_F(BluetoothTest, ExtraDidDiscoverServicesCall) { #endif // Tests Advertisement Data is updated correctly when we start discovery // during a connection. +#if defined(OS_WIN) +TEST_P(BluetoothTestWinrtOnly, AdvertisementData_DiscoveryDuringConnection) { +#else TEST_F(BluetoothTest, MAYBE_AdvertisementData_DiscoveryDuringConnection) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -575,6 +785,7 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_DiscoveryDuringConnection) { device->GetUUIDs()); discovery_sessions_[0]->Stop(GetCallback(Call::EXPECTED), GetErrorCallback(Call::NOT_EXPECTED)); + base::RunLoop().RunUntilIdle(); ASSERT_FALSE(adapter_->IsDiscovering()); ASSERT_FALSE(discovery_sessions_[0]->IsActive()); ASSERT_EQ(0u, device->GetUUIDs().size()); @@ -592,6 +803,7 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_DiscoveryDuringConnection) { // Start Discovery and receive advertisement during connection, // should notify of device changed. // - GetInquiryRSSI: Should return the packet's rssi. + // - GetAdvertisingDataFlags: Should return last advertised flags. // - GetUUIDs: Should return only Advertised UUIDs since services haven't // been discovered yet. // - GetServiceData: Should return last advertised Service Data. @@ -607,10 +819,12 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_DiscoveryDuringConnection) { EXPECT_EQ(UUIDSet({BluetoothUUID(kTestUUIDGenericAccess), BluetoothUUID(kTestUUIDGenericAttribute)}), device->GetUUIDs()); -#if defined(OS_MACOSX) || defined(OS_ANDROID) +#if defined(OS_WIN) + EXPECT_TRUE(device->GetAdvertisingDataFlags().has_value()); + EXPECT_EQ(0x04, device->GetAdvertisingDataFlags().value()); +#endif EXPECT_EQ(ServiceDataMap({{BluetoothUUID(kTestUUIDHeartRate), {1}}}), device->GetServiceData()); -#endif // defined(OS_MACOSX) || defined(OS_ANDROID) EXPECT_EQ(ToInt8(TestTxPower::LOWEST), device->GetInquiryTxPower().value()); // Discover services, should notify of device changed. @@ -629,6 +843,7 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_DiscoveryDuringConnection) { // Receive advertisement again, notify of device changed. // - GetInquiryRSSI: Should return last packet's rssi. + // - GetAdvertisingDataFlags: Should return last advertised flags. // - GetUUIDs: Should return only new Advertised UUIDs and Service UUIDs. // - GetServiceData: Should return last advertised Service Data. // - GetInquiryTxPower: Should return the last packet's advertised Tx Power. @@ -636,21 +851,25 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_DiscoveryDuringConnection) { EXPECT_EQ(3, observer.device_changed_count()); EXPECT_EQ(ToInt8(TestRSSI::LOWER), device->GetInquiryRSSI().value()); +#if defined(OS_WIN) + EXPECT_TRUE(device->GetAdvertisingDataFlags().has_value()); + EXPECT_EQ(0x05, device->GetAdvertisingDataFlags().value()); +#endif EXPECT_EQ(UUIDSet({BluetoothUUID(kTestUUIDLinkLoss), BluetoothUUID(kTestUUIDImmediateAlert), BluetoothUUID(kTestUUIDHeartRate)}), device->GetUUIDs()); -#if defined(OS_MACOSX) || defined(OS_ANDROID) EXPECT_EQ(ServiceDataMap( {{BluetoothUUID(kTestUUIDHeartRate), std::vector<uint8_t>({})}, {BluetoothUUID(kTestUUIDImmediateAlert), {0, 2}}}), device->GetServiceData()); -#endif // defined(OS_MACOSX) || defined(OS_ANDROID) EXPECT_EQ(ToInt8(TestTxPower::LOWER), device->GetInquiryTxPower().value()); // Stop discovery session, should notify of device changed. // - GetInquiryRSSI: Should return nullopt because we are no longer // discovering. + // - GetAdvertisingDataFlags: Should return no flags since we are no longer + // discovering. // - GetUUIDs: Should only return Service UUIDs. // - GetServiceData: Should return an empty map since we are no longer // discovering. @@ -658,15 +877,15 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_DiscoveryDuringConnection) { // discovering. discovery_sessions_[0]->Stop(GetCallback(Call::EXPECTED), GetErrorCallback(Call::NOT_EXPECTED)); + base::RunLoop().RunUntilIdle(); ASSERT_FALSE(adapter_->IsDiscovering()); ASSERT_FALSE(discovery_sessions_[0]->IsActive()); EXPECT_EQ(4, observer.device_changed_count()); EXPECT_FALSE(device->GetInquiryRSSI()); + EXPECT_FALSE(device->GetAdvertisingDataFlags().has_value()); EXPECT_EQ(UUIDSet({BluetoothUUID(kTestUUIDHeartRate)}), device->GetUUIDs()); -#if defined(OS_MACOSX) || defined(OS_ANDROID) EXPECT_EQ(ServiceDataMap(), device->GetServiceData()); -#endif // defined(OS_MACOSX) || defined(OS_ANDROID) EXPECT_FALSE(device->GetInquiryTxPower()); // Disconnect device, should notify of device changed. @@ -688,7 +907,11 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_DiscoveryDuringConnection) { #define MAYBE_AdvertisementData_ConnectionDuringDiscovery \ DISABLED_AdvertisementData_ConnectionDuringDiscovery #endif +#if defined(OS_WIN) +TEST_P(BluetoothTestWinrtOnly, AdvertisementData_ConnectionDuringDiscovery) { +#else TEST_F(BluetoothTest, MAYBE_AdvertisementData_ConnectionDuringDiscovery) { +#endif // Tests that the Advertisement Data is correctly updated when // the device connects during discovery. if (!PlatformSupportsLowEnergy()) { @@ -702,6 +925,7 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_ConnectionDuringDiscovery) { // Start discovery session and receive and advertisement. No device changed // notification because it's a new device. // - GetInquiryRSSI: Should return the packet's rssi. + // - GetAdvertisingDataFlags: Should return advertised flags. // - GetUUIDs: Should return Advertised UUIDs. // - GetServiceData: Should return advertised Service Data. // - GetInquiryTxPower: Should return the packet's advertised Tx Power. @@ -712,13 +936,15 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_ConnectionDuringDiscovery) { EXPECT_EQ(0, observer.device_changed_count()); EXPECT_EQ(ToInt8(TestRSSI::LOWEST), device->GetInquiryRSSI().value()); +#if defined(OS_WIN) + EXPECT_TRUE(device->GetAdvertisingDataFlags().has_value()); + EXPECT_EQ(0x04, device->GetAdvertisingDataFlags().value()); +#endif EXPECT_EQ(UUIDSet({BluetoothUUID(kTestUUIDGenericAccess), BluetoothUUID(kTestUUIDGenericAttribute)}), device->GetUUIDs()); -#if defined(OS_MACOSX) || defined(OS_ANDROID) EXPECT_EQ(ServiceDataMap({{BluetoothUUID(kTestUUIDHeartRate), {1}}}), device->GetServiceData()); -#endif // defined(OS_MACOSX) EXPECT_EQ(ToInt8(TestTxPower::LOWEST), device->GetInquiryTxPower().value()); // Connect, should notify of device changed. @@ -736,6 +962,7 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_ConnectionDuringDiscovery) { // Receive Advertisement with new UUIDs, should notify of device changed. // - GetInquiryRSSI: Should return the packet's rssi. + // - GetAdvertisingDataFlags: Should return advertised flags. // - GetUUIDs: Should return new Advertised UUIDs. // - GetServiceData: Should return new advertised Service Data. // - GetInquiryTxPower: Should return the packet's advertised Tx Power. @@ -743,15 +970,17 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_ConnectionDuringDiscovery) { EXPECT_EQ(1, observer.device_changed_count()); EXPECT_EQ(ToInt8(TestRSSI::LOWER), device->GetInquiryRSSI().value()); +#if defined(OS_WIN) + EXPECT_TRUE(device->GetAdvertisingDataFlags().has_value()); + EXPECT_EQ(0x05, device->GetAdvertisingDataFlags().value()); +#endif EXPECT_EQ(UUIDSet({BluetoothUUID(kTestUUIDLinkLoss), BluetoothUUID(kTestUUIDImmediateAlert)}), device->GetUUIDs()); -#if defined(OS_MACOSX) || defined(OS_ANDROID) EXPECT_EQ(ServiceDataMap( {{BluetoothUUID(kTestUUIDHeartRate), std::vector<uint8_t>({})}, {BluetoothUUID(kTestUUIDImmediateAlert), {0, 2}}}), device->GetServiceData()); -#endif // defined(OS_MACOSX) || defined(OS_ANDROID) EXPECT_EQ(ToInt8(TestTxPower::LOWER), device->GetInquiryTxPower().value()); // Discover Services, should notify of device changed. @@ -770,6 +999,7 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_ConnectionDuringDiscovery) { // Disconnect, should notify of device changed. // - GetInquiryRSSI: Should return last packet's rssi. + // - GetAdvertisingDataFlags: Should return same advertised flags. // - GetUUIDs: Should return only Advertised UUIDs. // - GetServiceData: Should still return same advertised Service Data. // - GetInquiryTxPower: Should return the last packet's advertised Tx Power. @@ -780,20 +1010,23 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_ConnectionDuringDiscovery) { EXPECT_EQ(3, observer.device_changed_count()); EXPECT_EQ(ToInt8(TestRSSI::LOWER), device->GetInquiryRSSI().value()); +#if defined(OS_WIN) + EXPECT_TRUE(device->GetAdvertisingDataFlags().has_value()); + EXPECT_EQ(0x05, device->GetAdvertisingDataFlags().value()); +#endif EXPECT_EQ(UUIDSet({BluetoothUUID(kTestUUIDLinkLoss), BluetoothUUID(kTestUUIDImmediateAlert)}), device->GetUUIDs()); -#if defined(OS_MACOSX) || defined(OS_ANDROID) EXPECT_EQ(ServiceDataMap( {{BluetoothUUID(kTestUUIDHeartRate), std::vector<uint8_t>({})}, {BluetoothUUID(kTestUUIDImmediateAlert), {0, 2}}}), device->GetServiceData()); -#endif // defined(OS_MACOSX) EXPECT_EQ(ToInt8(TestTxPower::LOWER), device->GetInquiryTxPower().value()); // Receive Advertisement with new UUIDs, should notify of device changed. // - GetInquiryRSSI: Should return last packet's rssi. + // - GetAdvertisingDataFlags: Should return the new advertised flags. // - GetUUIDs: Should return only new Advertised UUIDs. // - GetServiceData: Should return only new advertised Service Data. // - GetInquiryTxPower: Should return the last packet's advertised Tx Power. @@ -801,18 +1034,22 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_ConnectionDuringDiscovery) { EXPECT_EQ(4, observer.device_changed_count()); EXPECT_EQ(ToInt8(TestRSSI::LOWEST), device->GetInquiryRSSI().value()); +#if defined(OS_WIN) + EXPECT_TRUE(device->GetAdvertisingDataFlags().has_value()); + EXPECT_EQ(0x04, device->GetAdvertisingDataFlags().value()); +#endif EXPECT_EQ(UUIDSet({BluetoothUUID(kTestUUIDGenericAccess), BluetoothUUID(kTestUUIDGenericAttribute)}), device->GetUUIDs()); -#if defined(OS_MACOSX) || defined(OS_ANDROID) EXPECT_EQ(ServiceDataMap({{BluetoothUUID(kTestUUIDHeartRate), {1}}}), device->GetServiceData()); -#endif // defined(OS_MACOSX) || defined(OS_ANDROID) EXPECT_EQ(ToInt8(TestTxPower::LOWEST), device->GetInquiryTxPower().value()); // Stop discovery session, should notify of device changed. // - GetInquiryRSSI: Should return nullopt because we are no longer // discovering. + // - GetAdvertisingDataFlags: Should return no advertised flags since we are + // no longer discovering. // - GetUUIDs: Should return no UUIDs. // - GetServiceData: Should return no UUIDs since we are no longer // discovering. @@ -820,13 +1057,13 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_ConnectionDuringDiscovery) { // discovering. discovery_sessions_[0]->Stop(GetCallback(Call::EXPECTED), GetErrorCallback(Call::NOT_EXPECTED)); + base::RunLoop().RunUntilIdle(); EXPECT_EQ(5, observer.device_changed_count()); EXPECT_FALSE(device->GetInquiryRSSI()); + EXPECT_FALSE(device->GetAdvertisingDataFlags().has_value()); EXPECT_TRUE(device->GetUUIDs().empty()); -#if defined(OS_MACOSX) || defined(OS_ANDROID) EXPECT_TRUE(device->GetServiceData().empty()); -#endif // defined(OS_MACOSX) || defined(OS_ANDROID) EXPECT_FALSE(device->GetInquiryTxPower()); } @@ -922,7 +1159,7 @@ TEST_F(BluetoothTest, MAYBE_DisconnectionNotifiesDeviceChanged) { EXPECT_TRUE(device->IsConnected()); EXPECT_TRUE(device->IsGattConnected()); - SimulateGattDisconnection(device); + SimulateDeviceBreaksConnection(device); base::RunLoop().RunUntilIdle(); EXPECT_EQ(2, observer.device_changed_count()); EXPECT_FALSE(device->IsConnected()); @@ -1622,7 +1859,7 @@ TEST_F(BluetoothTest, MAYBE_GattServicesDiscovered_AfterDisconnection) { base::RunLoop().RunUntilIdle(); EXPECT_EQ(1, gatt_discovery_attempts_); - SimulateGattDisconnection(device); + SimulateDeviceBreaksConnection(device); base::RunLoop().RunUntilIdle(); SimulateGattServicesDiscovered( @@ -1716,6 +1953,36 @@ TEST_F(BluetoothTest, MAYBE_GetGattServices_and_GetGattService) { } #if defined(OS_ANDROID) || defined(OS_MACOSX) +#define MAYBE_GetGattServices_FindNone GetGattServices_FindNone +#else +#define MAYBE_GetGattServices_FindNone DISABLED_GetGattServices_FindNone +#endif +#if defined(OS_WIN) +TEST_P(BluetoothTestWinrtOnly, GetGattServices_FindNone) { +#else +TEST_F(BluetoothTest, MAYBE_GetGattServices_FindNone) { +#endif + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + InitWithFakeAdapter(); + StartLowEnergyDiscoverySession(); + BluetoothDevice* device = SimulateLowEnergyDevice(3); + device->CreateGattConnection(GetGattConnectionCallback(Call::EXPECTED), + GetConnectErrorCallback(Call::NOT_EXPECTED)); + ResetEventCounts(); + SimulateGattConnection(device); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1, gatt_discovery_attempts_); + + // Simulate an empty set of discovered services. + SimulateGattServicesDiscovered(device, {} /* uuids */); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0u, device->GetGattServices().size()); +} + +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_GetGattServices_DiscoveryError GetGattServices_DiscoveryError #else #define MAYBE_GetGattServices_DiscoveryError \ diff --git a/chromium/device/bluetooth/bluetooth_device_winrt.cc b/chromium/device/bluetooth/bluetooth_device_winrt.cc index bae8fb99a9a..0313a90e235 100644 --- a/chromium/device/bluetooth/bluetooth_device_winrt.cc +++ b/chromium/device/bluetooth/bluetooth_device_winrt.cc @@ -4,18 +4,21 @@ #include "device/bluetooth/bluetooth_device_winrt.h" +#include <windows.devices.enumeration.h> #include <windows.foundation.h> #include <utility> #include "base/bind_helpers.h" #include "base/logging.h" +#include "base/stl_util.h" #include "base/strings/string_piece.h" #include "base/strings/stringprintf.h" #include "base/win/core_winrt_util.h" #include "base/win/scoped_hstring.h" #include "device/bluetooth/bluetooth_adapter_winrt.h" #include "device/bluetooth/bluetooth_gatt_discoverer_winrt.h" +#include "device/bluetooth/bluetooth_pairing_winrt.h" #include "device/bluetooth/bluetooth_remote_gatt_service_winrt.h" #include "device/bluetooth/event_utils_winrt.h" @@ -34,13 +37,69 @@ using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: GattDeviceServicesResult; using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: IGattDeviceServicesResult; -using ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice; +using ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice2; using ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice3; +using ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice; using ABI::Windows::Devices::Bluetooth::IBluetoothLEDeviceStatics; +using ABI::Windows::Devices::Enumeration::DevicePairingResultStatus; +using ABI::Windows::Devices::Enumeration::IDeviceInformation2; +using ABI::Windows::Devices::Enumeration::IDeviceInformation; +using ABI::Windows::Devices::Enumeration::IDeviceInformationCustomPairing; +using ABI::Windows::Devices::Enumeration::IDeviceInformationPairing2; +using ABI::Windows::Devices::Enumeration::IDeviceInformationPairing; +using ABI::Windows::Devices::Enumeration::IDevicePairingRequestedEventArgs; using ABI::Windows::Foundation::IAsyncOperation; using ABI::Windows::Foundation::IClosable; using Microsoft::WRL::ComPtr; +void PostTask(BluetoothPairingWinrt::ErrorCallback error_callback, + BluetoothDevice::ConnectErrorCode error_code) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(error_callback), error_code)); +} + +ComPtr<IDeviceInformationPairing> GetDeviceInformationPairing( + ComPtr<IBluetoothLEDevice> ble_device) { + if (!ble_device) { + VLOG(2) << "No BLE device instance present."; + return nullptr; + } + + ComPtr<IBluetoothLEDevice2> ble_device_2; + HRESULT hr = ble_device.As(&ble_device_2); + if (FAILED(hr)) { + VLOG(2) << "Obtaining IBluetoothLEDevice2 failed: " + << logging::SystemErrorCodeToString(hr); + return nullptr; + } + + ComPtr<IDeviceInformation> device_information; + hr = ble_device_2->get_DeviceInformation(&device_information); + if (FAILED(hr)) { + VLOG(2) << "Getting Device Information failed: " + << logging::SystemErrorCodeToString(hr); + return nullptr; + } + + ComPtr<IDeviceInformation2> device_information_2; + hr = device_information.As(&device_information_2); + if (FAILED(hr)) { + VLOG(2) << "Obtaining IDeviceInformation2 failed: " + << logging::SystemErrorCodeToString(hr); + return nullptr; + } + + ComPtr<IDeviceInformationPairing> pairing; + hr = device_information_2->get_Pairing(&pairing); + if (FAILED(hr)) { + VLOG(2) << "DeviceInformation::get_Pairing() failed: " + << logging::SystemErrorCodeToString(hr); + return nullptr; + } + + return pairing; +} + void CloseDevice(ComPtr<IBluetoothLEDevice> ble_device) { if (!ble_device) return; @@ -147,8 +206,24 @@ base::Optional<std::string> BluetoothDeviceWinrt::GetName() const { } bool BluetoothDeviceWinrt::IsPaired() const { - NOTIMPLEMENTED(); - return false; + ComPtr<IDeviceInformationPairing> pairing = + GetDeviceInformationPairing(ble_device_); + if (!pairing) { + VLOG(2) << "Failed to get DeviceInformationPairing."; + return false; + } + + boolean is_paired; + HRESULT hr = pairing->get_IsPaired(&is_paired); + if (FAILED(hr)) { + VLOG(2) << "DeviceInformationPairing::get_IsPaired() failed: " + << logging::SystemErrorCodeToString(hr); + return false; + } + + VLOG(2) << "BluetoothDeviceWinrt::IsPaired(): " + << (is_paired ? "True" : "False"); + return is_paired; } bool BluetoothDeviceWinrt::IsConnected() const { @@ -181,8 +256,7 @@ bool BluetoothDeviceWinrt::IsConnecting() const { } bool BluetoothDeviceWinrt::ExpectingPinCode() const { - NOTIMPLEMENTED(); - return false; + return pairing_ && pairing_->ExpectingPinCode(); } bool BluetoothDeviceWinrt::ExpectingPasskey() const { @@ -213,8 +287,71 @@ void BluetoothDeviceWinrt::Connect(PairingDelegate* pairing_delegate, NOTIMPLEMENTED(); } +void BluetoothDeviceWinrt::Pair(PairingDelegate* pairing_delegate, + const base::Closure& callback, + const ConnectErrorCallback& error_callback) { + VLOG(2) << "BluetoothDeviceWinrt::Pair()"; + if (pairing_) { + VLOG(2) << "Another Pair Operation is already in progress."; + PostTask(error_callback, ERROR_INPROGRESS); + return; + } + + ComPtr<IDeviceInformationPairing> pairing = + GetDeviceInformationPairing(ble_device_); + if (!pairing) { + VLOG(2) << "Failed to get DeviceInformationPairing."; + PostTask(error_callback, ERROR_UNKNOWN); + return; + } + + ComPtr<IDeviceInformationPairing2> pairing_2; + HRESULT hr = pairing.As(&pairing_2); + if (FAILED(hr)) { + VLOG(2) << "Obtaining IDeviceInformationPairing2 failed: " + << logging::SystemErrorCodeToString(hr); + PostTask(error_callback, ERROR_UNKNOWN); + return; + } + + ComPtr<IDeviceInformationCustomPairing> custom; + hr = pairing_2->get_Custom(&custom); + if (FAILED(hr)) { + VLOG(2) << "DeviceInformationPairing::get_Custom() failed: " + << logging::SystemErrorCodeToString(hr); + PostTask(error_callback, ERROR_UNKNOWN); + return; + } + + // Wrap success and error callback, so that they clean up the pairing object + // once they are run. + auto wrapped_callback = base::BindOnce( + [](base::WeakPtr<BluetoothDeviceWinrt> device, + base::OnceClosure callback) { + if (device) + device->pairing_.reset(); + std::move(callback).Run(); + }, + weak_ptr_factory_.GetWeakPtr(), callback); + + auto wrapped_error_callback = base::BindOnce( + [](base::WeakPtr<BluetoothDeviceWinrt> device, + ConnectErrorCallback error_callback, ConnectErrorCode error_code) { + if (device) + device->pairing_.reset(); + std::move(error_callback).Run(error_code); + }, + weak_ptr_factory_.GetWeakPtr(), error_callback); + + pairing_ = std::make_unique<BluetoothPairingWinrt>( + this, pairing_delegate, std::move(custom), std::move(wrapped_callback), + std::move(wrapped_error_callback)); + pairing_->StartPairing(); +} + void BluetoothDeviceWinrt::SetPinCode(const std::string& pincode) { - NOTIMPLEMENTED(); + if (pairing_) + pairing_->SetPinCode(pincode); } void BluetoothDeviceWinrt::SetPasskey(uint32_t passkey) { @@ -226,11 +363,13 @@ void BluetoothDeviceWinrt::ConfirmPairing() { } void BluetoothDeviceWinrt::RejectPairing() { - NOTIMPLEMENTED(); + if (pairing_) + pairing_->RejectPairing(); } void BluetoothDeviceWinrt::CancelPairing() { - NOTIMPLEMENTED(); + if (pairing_) + pairing_->CancelPairing(); } void BluetoothDeviceWinrt::Disconnect(const base::Closure& callback, @@ -311,7 +450,20 @@ void BluetoothDeviceWinrt::CreateGattConnectionImpl() { } void BluetoothDeviceWinrt::DisconnectGatt() { + // Closing the device and disposing of all references will trigger a Gatt + // Disconnection after a short timeout. Since the Gatt Services store a + // reference to |ble_device_| as well, we need to clear them to drop all + // remaining references, so that the OS disconnects. + // Reference: + // - https://docs.microsoft.com/en-us/windows/uwp/devices-sensors/gatt-client CloseDevice(ble_device_); + ClearGattServices(); + + // Stop any pending Gatt Discovery sessions and report an error. This will + // destroy |gatt_discoverer_| and release remaining references the discoverer + // might have hold. + if (gatt_discoverer_) + OnGattDiscoveryComplete(false); } HRESULT BluetoothDeviceWinrt::GetBluetoothLEDeviceStaticsActivationFactory( @@ -344,6 +496,13 @@ void BluetoothDeviceWinrt::OnFromBluetoothAddress( base::BindRepeating(&BluetoothDeviceWinrt::OnConnectionStatusChanged, weak_ptr_factory_.GetWeakPtr())); + // For paired devices the OS immediately establishes a GATT connection after + // the first advertisement. In this case our handler is registered too late to + // catch the initial connection changed event, and we need to perform an + // explicit check ourselves. + if (IsGattConnected()) + DidConnectGatt(); + if (gatt_services_changed_token_) { RemoveGattServicesChangedHandler(ble_device_.Get(), *gatt_services_changed_token_); @@ -371,9 +530,7 @@ void BluetoothDeviceWinrt::OnConnectionStatusChanged( DidConnectGatt(); } else { gatt_discoverer_.reset(); - gatt_services_.clear(); - device_uuids_.ClearServiceUUIDs(); - SetGattServicesDiscoveryComplete(false); + ClearGattServices(); DidDisconnectGatt(); } } @@ -381,6 +538,8 @@ void BluetoothDeviceWinrt::OnConnectionStatusChanged( void BluetoothDeviceWinrt::OnGattServicesChanged(IBluetoothLEDevice* ble_device, IInspectable* object) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + // Note: We don't clear out |gatt_services_| here, as we don't want to break + // existing references to Gatt Services that did not change. device_uuids_.ClearServiceUUIDs(); SetGattServicesDiscoveryComplete(false); adapter_->NotifyDeviceChanged(this); @@ -399,20 +558,35 @@ void BluetoothDeviceWinrt::OnGattDiscoveryComplete(bool success) { if (!success) { if (!IsGattConnected()) DidFailToConnectGatt(ConnectErrorCode::ERROR_FAILED); + gatt_discoverer_.reset(); return; } + // Instead of clearing out |gatt_services_| and creating each service from + // scratch, we create a new map and move already existing services into it in + // order to preserve pointer stability. + GattServiceMap gatt_services; for (const auto& gatt_service : gatt_discoverer_->GetGattServices()) { auto gatt_service_winrt = BluetoothRemoteGattServiceWinrt::Create(this, gatt_service); if (!gatt_service_winrt) continue; - const auto& service = *gatt_service_winrt; - gatt_services_.emplace(service.GetIdentifier(), - std::move(gatt_service_winrt)); + std::string identifier = gatt_service_winrt->GetIdentifier(); + auto iter = gatt_services_.find(identifier); + if (iter != gatt_services_.end()) { + iter = gatt_services.emplace(std::move(*iter)).first; + } else { + iter = gatt_services + .emplace(std::move(identifier), std::move(gatt_service_winrt)) + .first; + } + + static_cast<BluetoothRemoteGattServiceWinrt*>(iter->second.get()) + ->UpdateCharacteristics(gatt_discoverer_.get()); } + std::swap(gatt_services, gatt_services_); device_uuids_.ReplaceServiceUUIDs(gatt_services_); SetGattServicesDiscoveryComplete(true); adapter_->NotifyGattServicesDiscovered(this); @@ -420,4 +594,10 @@ void BluetoothDeviceWinrt::OnGattDiscoveryComplete(bool success) { gatt_discoverer_.reset(); } +void BluetoothDeviceWinrt::ClearGattServices() { + gatt_services_.clear(); + device_uuids_.ClearServiceUUIDs(); + SetGattServicesDiscoveryComplete(false); +} + } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_device_winrt.h b/chromium/device/bluetooth/bluetooth_device_winrt.h index 2b7b4378909..93644ab66cd 100644 --- a/chromium/device/bluetooth/bluetooth_device_winrt.h +++ b/chromium/device/bluetooth/bluetooth_device_winrt.h @@ -25,9 +25,18 @@ namespace device { class BluetoothAdapterWinrt; class BluetoothGattDiscovererWinrt; +class BluetoothPairingWinrt; class DEVICE_BLUETOOTH_EXPORT BluetoothDeviceWinrt : public BluetoothDevice { public: + // Constants required to extract the tx power level and service data from the + // raw advertisementment data. Reference: + // https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile + static constexpr uint8_t kTxPowerLevelDataSection = 0x0A; + static constexpr uint8_t k16BitServiceDataSection = 0x16; + static constexpr uint8_t k32BitServiceDataSection = 0x20; + static constexpr uint8_t k128BitServiceDataSection = 0x21; + BluetoothDeviceWinrt(BluetoothAdapterWinrt* adapter, uint64_t raw_address, base::Optional<std::string> local_name); @@ -57,6 +66,9 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDeviceWinrt : public BluetoothDevice { void Connect(PairingDelegate* pairing_delegate, const base::Closure& callback, const ConnectErrorCallback& error_callback) override; + void Pair(PairingDelegate* pairing_delegate, + const base::Closure& callback, + const ConnectErrorCallback& error_callback) override; void SetPinCode(const std::string& pincode) override; void SetPasskey(uint32_t passkey) override; void ConfirmPairing() override; @@ -107,10 +119,14 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDeviceWinrt : public BluetoothDevice { void OnGattDiscoveryComplete(bool success); + void ClearGattServices(); + uint64_t raw_address_; std::string address_; base::Optional<std::string> local_name_; + std::unique_ptr<BluetoothPairingWinrt> pairing_; + std::unique_ptr<BluetoothGattDiscovererWinrt> gatt_discoverer_; base::Optional<EventRegistrationToken> connection_changed_token_; diff --git a/chromium/device/bluetooth/bluetooth_gatt_discoverer_winrt.cc b/chromium/device/bluetooth/bluetooth_gatt_discoverer_winrt.cc index aacaf680a62..d964e11ac43 100644 --- a/chromium/device/bluetooth/bluetooth_gatt_discoverer_winrt.cc +++ b/chromium/device/bluetooth/bluetooth_gatt_discoverer_winrt.cc @@ -9,6 +9,7 @@ #include <utility> #include "base/logging.h" +#include "base/stl_util.h" #include "device/bluetooth/bluetooth_remote_gatt_service_winrt.h" #include "device/bluetooth/event_utils_winrt.h" @@ -17,23 +18,88 @@ namespace device { namespace { using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + GattCharacteristic; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + GattCharacteristicsResult; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: GattCommunicationStatus; using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: GattCommunicationStatus_Success; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::GattDescriptor; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + GattDescriptorsResult; using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: GattDeviceService; using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: GattDeviceServicesResult; using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: - IGattDeviceService; + IGattCharacteristic3; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattCharacteristicsResult; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattDescriptorsResult; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattDeviceService3; using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: IGattDeviceServicesResult; -using ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice; using ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice3; -using ABI::Windows::Foundation::IAsyncOperation; +using ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice; using ABI::Windows::Foundation::Collections::IVectorView; +using ABI::Windows::Foundation::IAsyncOperation; +using ABI::Windows::Foundation::IReference; using Microsoft::WRL::ComPtr; +template <typename IGattResult> +bool CheckCommunicationStatus(IGattResult* gatt_result) { + if (!gatt_result) { + VLOG(2) << "Getting GATT Results failed."; + return false; + } + + GattCommunicationStatus status; + HRESULT hr = gatt_result->get_Status(&status); + if (FAILED(hr)) { + VLOG(2) << "Getting GATT Communication Status failed: " + << logging::SystemErrorCodeToString(hr); + return false; + } + + if (status != GattCommunicationStatus_Success) { + VLOG(2) << "Unexpected GattCommunicationStatus: " << status; + VLOG(2) << "GATT Error Code: " + << static_cast<int>( + BluetoothRemoteGattServiceWinrt::GetGattErrorCode( + gatt_result)); + } + + return status == GattCommunicationStatus_Success; +} + +template <typename T, typename I> +bool GetAsVector(IVectorView<T*>* view, std::vector<ComPtr<I>>* vector) { + unsigned size; + HRESULT hr = view->get_Size(&size); + if (FAILED(hr)) { + VLOG(2) << "Getting Size failed: " << logging::SystemErrorCodeToString(hr); + return false; + } + + vector->reserve(size); + for (unsigned i = 0; i < size; ++i) { + ComPtr<I> entry; + hr = view->GetAt(i, &entry); + if (FAILED(hr)) { + VLOG(2) << "GetAt(" << i + << ") failed: " << logging::SystemErrorCodeToString(hr); + return false; + } + + vector->push_back(std::move(entry)); + } + + return true; +} + } // namespace BluetoothGattDiscovererWinrt::BluetoothGattDiscovererWinrt( @@ -80,64 +146,195 @@ BluetoothGattDiscovererWinrt::GetGattServices() const { return gatt_services_; } +const BluetoothGattDiscovererWinrt::GattCharacteristicList* +BluetoothGattDiscovererWinrt::GetCharacteristics( + uint16_t service_attribute_handle) const { + auto iter = service_to_characteristics_map_.find(service_attribute_handle); + return iter != service_to_characteristics_map_.end() ? &iter->second + : nullptr; +} + +const BluetoothGattDiscovererWinrt::GattDescriptorList* +BluetoothGattDiscovererWinrt::GetDescriptors( + uint16_t characteristic_attribute_handle) const { + auto iter = + characteristic_to_descriptors_map_.find(characteristic_attribute_handle); + return iter != characteristic_to_descriptors_map_.end() ? &iter->second + : nullptr; +} + void BluetoothGattDiscovererWinrt::OnGetGattServices( ComPtr<IGattDeviceServicesResult> services_result) { - if (!services_result) { - VLOG(2) << "Getting GATT Services failed."; + if (!CheckCommunicationStatus(services_result.Get())) { std::move(callback_).Run(false); return; } - GattCommunicationStatus status; - HRESULT hr = services_result->get_Status(&status); + ComPtr<IVectorView<GattDeviceService*>> services; + HRESULT hr = services_result->get_Services(&services); if (FAILED(hr)) { - VLOG(2) << "Getting GATT Communication Status failed: " + VLOG(2) << "Getting GATT Services failed: " << logging::SystemErrorCodeToString(hr); std::move(callback_).Run(false); return; } - if (status != GattCommunicationStatus_Success) { - VLOG(2) << "Unexpected GattCommunicationStatus: " << status; - // TODO(https://crbug.com/821766): Obtain and log the protocol error if - // appropriate. Mention BT spec Version 5.0 Vol 3, Part F, 3.4.1.1 "Error - // Response". + if (!GetAsVector(services.Get(), &gatt_services_)) { std::move(callback_).Run(false); return; } - ComPtr<IVectorView<GattDeviceService*>> services; - hr = services_result->get_Services(&services); - if (FAILED(hr)) { - VLOG(2) << "Getting GATT Services failed: " - << logging::SystemErrorCodeToString(hr); + num_services_ = gatt_services_.size(); + for (const auto& gatt_service : gatt_services_) { + uint16_t service_attribute_handle; + hr = gatt_service->get_AttributeHandle(&service_attribute_handle); + if (FAILED(hr)) { + VLOG(2) << "Getting AttributeHandle failed: " + << logging::SystemErrorCodeToString(hr); + std::move(callback_).Run(false); + return; + } + + ComPtr<IGattDeviceService3> gatt_service_3; + hr = gatt_service.As(&gatt_service_3); + if (FAILED(hr)) { + VLOG(2) << "Obtaining IGattDeviceService3 failed: " + << logging::SystemErrorCodeToString(hr); + std::move(callback_).Run(false); + return; + } + + ComPtr<IAsyncOperation<GattCharacteristicsResult*>> get_characteristics_op; + hr = gatt_service_3->GetCharacteristicsAsync(&get_characteristics_op); + if (FAILED(hr)) { + VLOG(2) << "GattDeviceService::GetCharacteristicsAsync() failed: " + << logging::SystemErrorCodeToString(hr); + std::move(callback_).Run(false); + return; + } + + hr = PostAsyncResults( + std::move(get_characteristics_op), + base::BindOnce(&BluetoothGattDiscovererWinrt::OnGetCharacteristics, + weak_ptr_factory_.GetWeakPtr(), + service_attribute_handle)); + + if (FAILED(hr)) { + VLOG(2) << "PostAsyncResults failed: " + << logging::SystemErrorCodeToString(hr); + std::move(callback_).Run(false); + } + } + + RunCallbackIfDone(); +} + +void BluetoothGattDiscovererWinrt::OnGetCharacteristics( + uint16_t service_attribute_handle, + ComPtr<IGattCharacteristicsResult> characteristics_result) { + if (!CheckCommunicationStatus(characteristics_result.Get())) { std::move(callback_).Run(false); return; } - unsigned size; - hr = services->get_Size(&size); + ComPtr<IVectorView<GattCharacteristic*>> characteristics; + HRESULT hr = characteristics_result->get_Characteristics(&characteristics); if (FAILED(hr)) { - VLOG(2) << "Getting Size of GATT Services failed: " + VLOG(2) << "Getting Characteristics failed: " << logging::SystemErrorCodeToString(hr); std::move(callback_).Run(false); return; } - for (unsigned i = 0; i < size; ++i) { - ComPtr<IGattDeviceService> service; - hr = services->GetAt(i, &service); + DCHECK(!base::ContainsKey(service_to_characteristics_map_, + service_attribute_handle)); + auto& characteristics_list = + service_to_characteristics_map_[service_attribute_handle]; + if (!GetAsVector(characteristics.Get(), &characteristics_list)) { + std::move(callback_).Run(false); + return; + } + + num_characteristics_ += characteristics_list.size(); + for (const auto& gatt_characteristic : characteristics_list) { + uint16_t characteristic_attribute_handle; + hr = gatt_characteristic->get_AttributeHandle( + &characteristic_attribute_handle); if (FAILED(hr)) { - VLOG(2) << "GetAt(" << i - << ") failed: " << logging::SystemErrorCodeToString(hr); + VLOG(2) << "Getting AttributeHandle failed: " + << logging::SystemErrorCodeToString(hr); + std::move(callback_).Run(false); + return; + } + + ComPtr<IGattCharacteristic3> gatt_characteristic_3; + hr = gatt_characteristic.As(&gatt_characteristic_3); + if (FAILED(hr)) { + VLOG(2) << "Obtaining IGattCharacteristic3 failed: " + << logging::SystemErrorCodeToString(hr); + std::move(callback_).Run(false); + return; + } + + ComPtr<IAsyncOperation<GattDescriptorsResult*>> get_descriptors_op; + hr = gatt_characteristic_3->GetDescriptorsAsync(&get_descriptors_op); + if (FAILED(hr)) { + VLOG(2) << "GattCharacteristic::GetDescriptorsAsync() failed: " + << logging::SystemErrorCodeToString(hr); std::move(callback_).Run(false); return; } - gatt_services_.push_back(std::move(service)); + hr = PostAsyncResults( + std::move(get_descriptors_op), + base::BindOnce(&BluetoothGattDiscovererWinrt::OnGetDescriptors, + weak_ptr_factory_.GetWeakPtr(), + characteristic_attribute_handle)); + + if (FAILED(hr)) { + VLOG(2) << "PostAsyncResults failed: " + << logging::SystemErrorCodeToString(hr); + std::move(callback_).Run(false); + } } - std::move(callback_).Run(true); + RunCallbackIfDone(); +} + +void BluetoothGattDiscovererWinrt::OnGetDescriptors( + uint16_t characteristic_attribute_handle, + ComPtr<IGattDescriptorsResult> descriptors_result) { + if (!CheckCommunicationStatus(descriptors_result.Get())) { + std::move(callback_).Run(false); + return; + } + + ComPtr<IVectorView<GattDescriptor*>> descriptors; + HRESULT hr = descriptors_result->get_Descriptors(&descriptors); + if (FAILED(hr)) { + VLOG(2) << "Getting Descriptors failed: " + << logging::SystemErrorCodeToString(hr); + std::move(callback_).Run(false); + return; + } + + DCHECK(!base::ContainsKey(characteristic_to_descriptors_map_, + characteristic_attribute_handle)); + if (!GetAsVector(descriptors.Get(), &characteristic_to_descriptors_map_ + [characteristic_attribute_handle])) { + std::move(callback_).Run(false); + return; + } + + RunCallbackIfDone(); +} + +void BluetoothGattDiscovererWinrt::RunCallbackIfDone() { + DCHECK(callback_); + if (service_to_characteristics_map_.size() == num_services_ && + characteristic_to_descriptors_map_.size() == num_characteristics_) { + std::move(callback_).Run(true); + } } } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_gatt_discoverer_winrt.h b/chromium/device/bluetooth/bluetooth_gatt_discoverer_winrt.h index 6f53c819a35..39392baaae7 100644 --- a/chromium/device/bluetooth/bluetooth_gatt_discoverer_winrt.h +++ b/chromium/device/bluetooth/bluetooth_gatt_discoverer_winrt.h @@ -9,10 +9,13 @@ #include <windows.devices.bluetooth.h> #include <wrl/client.h> +#include <stdint.h> + #include <memory> #include <vector> #include "base/callback.h" +#include "base/containers/flat_map.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "base/threading/thread_checker.h" @@ -32,14 +35,27 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothGattDiscovererWinrt { using GattServiceList = std::vector< Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth:: GenericAttributeProfile::IGattDeviceService>>; + using GattCharacteristicList = std::vector< + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth:: + GenericAttributeProfile::IGattCharacteristic>>; + using GattDescriptorList = std::vector< + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth:: + GenericAttributeProfile::IGattDescriptor>>; BluetoothGattDiscovererWinrt( Microsoft::WRL::ComPtr< ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice> ble_device); ~BluetoothGattDiscovererWinrt(); + // Note: In order to avoid running |callback| multiple times on errors, + // clients are expected to synchronously destroy the GattDiscoverer after + // |callback| has been invoked for the first time. void StartGattDiscovery(GattDiscoveryCallback callback); const GattServiceList& GetGattServices() const; + const GattCharacteristicList* GetCharacteristics( + uint16_t service_attribute_handle) const; + const GattDescriptorList* GetDescriptors( + uint16_t characteristic_attribute_handle) const; private: void OnGetGattServices( @@ -47,10 +63,32 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothGattDiscovererWinrt { ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: IGattDeviceServicesResult> services_result); + void OnGetCharacteristics( + uint16_t service_attribute_handle, + Microsoft::WRL::ComPtr< + ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattCharacteristicsResult> characteristics_result); + + void OnGetDescriptors( + uint16_t characteristic_attribute_handle, + Microsoft::WRL::ComPtr< + ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattDescriptorsResult> descriptors_result); + + void RunCallbackIfDone(); + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice> ble_device_; + GattDiscoveryCallback callback_; GattServiceList gatt_services_; + base::flat_map<uint16_t, GattCharacteristicList> + service_to_characteristics_map_; + base::flat_map<uint16_t, GattDescriptorList> + characteristic_to_descriptors_map_; + size_t num_services_ = 0; + size_t num_characteristics_ = 0; + THREAD_CHECKER(thread_checker_); // Note: This should remain the last member so it'll be destroyed and diff --git a/chromium/device/bluetooth/bluetooth_low_energy_advertisement_manager_mac.mm b/chromium/device/bluetooth/bluetooth_low_energy_advertisement_manager_mac.mm index c8fe0db8e7b..a7b03c84975 100644 --- a/chromium/device/bluetooth/bluetooth_low_energy_advertisement_manager_mac.mm +++ b/chromium/device/bluetooth/bluetooth_low_energy_advertisement_manager_mac.mm @@ -32,8 +32,10 @@ void BluetoothLowEnergyAdvertisementManagerMac:: // powered on. // 2. Start advertising a registered advertisement if the adapter is powered // on. - // Note that if the adapter is powered off while advertising, macOS will - // automatically restart advertising when the adapter is powered back on. + // 3. Stop the advertisement when the adapter is powered off. + // Note that if the adapter is powered off while advertising, macOS will + // automatically restart advertising when the adapter is powered back on, + // so we need to explicitly stop advertising in this case. if (!active_advertisement_) { return; @@ -61,12 +63,22 @@ void BluetoothLowEnergyAdvertisementManagerMac:: if (active_advertisement_->is_advertising() && adapter_state == CBPeripheralManagerStateResetting) { - DVLOG(1) << "Adapter resetting. Invaldating advertisement."; + DVLOG(1) << "Adapter resetting. Invalidating advertisement."; active_advertisement_->OnAdapterReset(); active_advertisement_ = nullptr; return; } + if (active_advertisement_->is_advertising() && + adapter_state == CBPeripheralManagerStatePoweredOff) { + DVLOG(1) << "Adapter powered off. Stopping advertisement."; + // Note: we purposefully don't unregister the active advertisement for + // consistency with ChromeOS. The caller must manually unregister + // the advertisement themselves. + [peripheral_manager_ stopAdvertising]; + return; + } + if (active_advertisement_->is_waiting_for_adapter()) { StartAdvertising(); } diff --git a/chromium/device/bluetooth/bluetooth_low_energy_advertisement_manager_mac_unittest.mm b/chromium/device/bluetooth/bluetooth_low_energy_advertisement_manager_mac_unittest.mm index 68ac8dc27de..0a85aad293f 100644 --- a/chromium/device/bluetooth/bluetooth_low_energy_advertisement_manager_mac_unittest.mm +++ b/chromium/device/bluetooth/bluetooth_low_energy_advertisement_manager_mac_unittest.mm @@ -159,7 +159,7 @@ TEST_F(BluetoothLowEnergyAdvertisementManagerMacTest, EXPECT_FALSE(advertisement_); EXPECT_FALSE(registration_error_); - // Change the adapter state to CBPeripheralManagerStateUnsupported, which + // Change the adapter state to CBPeripheralManagerStatePoweredOff, which // causes the registration to fail. peripheral_manager_state_ = CBPeripheralManagerStatePoweredOff; advertisement_manager_.OnPeripheralManagerStateChanged(); @@ -272,23 +272,6 @@ TEST_F(BluetoothLowEnergyAdvertisementManagerMacTest, Register_Twice) { } TEST_F(BluetoothLowEnergyAdvertisementManagerMacTest, - AdapterPoweredOff_WhileAdvertising) { - // Register advertisement. - RegisterAdvertisement(CreateAdvertisementData()); - advertisement_manager_.DidStartAdvertising(nil); - ui_task_runner_->RunPendingTasks(); - EXPECT_TRUE(advertisement_); - - // Power off the adapter. Advertisement should not be stopped. - BluetoothAdvertisementMac* advertisement_mac = - static_cast<BluetoothAdvertisementMac*>(advertisement_.get()); - EXPECT_TRUE(advertisement_mac->is_advertising()); - peripheral_manager_state_ = CBPeripheralManagerStatePoweredOff; - advertisement_manager_.OnPeripheralManagerStateChanged(); - EXPECT_TRUE(advertisement_mac->is_advertising()); -} - -TEST_F(BluetoothLowEnergyAdvertisementManagerMacTest, AdapterReset_RestartAdvertising) { // Register advertisement. RegisterAdvertisement(CreateAdvertisementData()); @@ -311,4 +294,31 @@ TEST_F(BluetoothLowEnergyAdvertisementManagerMacTest, [peripheral_manager_mock_ verifyAtLocation:nil]; } +TEST_F(BluetoothLowEnergyAdvertisementManagerMacTest, + Advertising_ThenAdapterPoweredOff_ThenReregister) { + // Register advertisement. + RegisterAdvertisement(CreateAdvertisementData()); + advertisement_manager_.DidStartAdvertising(nil); + ui_task_runner_->RunPendingTasks(); + EXPECT_TRUE(advertisement_); + + // Power off the adapter. Advertisement should be stopped. + OCMExpect([peripheral_manager_ stopAdvertising]); + peripheral_manager_state_ = CBPeripheralManagerStatePoweredOff; + advertisement_manager_.OnPeripheralManagerStateChanged(); + [peripheral_manager_mock_ verifyAtLocation:nil]; + + // Register a new advertisement after powering back on the adapter. + // This should fail as the caller needs to manually unregister the + // advertisement. + peripheral_manager_state_ = CBPeripheralManagerStatePoweredOn; + advertisement_ = nullptr; + RegisterAdvertisement(CreateAdvertisementData()); + ui_task_runner_->RunPendingTasks(); + EXPECT_FALSE(advertisement_); + ASSERT_TRUE(registration_error_); + EXPECT_EQ(BluetoothAdvertisement::ERROR_ADVERTISEMENT_ALREADY_EXISTS, + *registration_error_); +} + } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_low_energy_central_manager_delegate.mm b/chromium/device/bluetooth/bluetooth_low_energy_central_manager_delegate.mm index c1306a96fbb..642767d381f 100644 --- a/chromium/device/bluetooth/bluetooth_low_energy_central_manager_delegate.mm +++ b/chromium/device/bluetooth/bluetooth_low_energy_central_manager_delegate.mm @@ -68,11 +68,6 @@ class BluetoothLowEnergyCentralManagerBridge { return self; } -- (void)dealloc { - [bridge_->GetCentralManager() setDelegate:nil]; - [super dealloc]; -} - - (void)centralManager:(CBCentralManager*)central didDiscoverPeripheral:(CBPeripheral*)peripheral advertisementData:(NSDictionary*)advertisementData diff --git a/chromium/device/bluetooth/bluetooth_low_energy_device_mac.mm b/chromium/device/bluetooth/bluetooth_low_energy_device_mac.mm index 187f3c91c07..a65d9fe3932 100644 --- a/chromium/device/bluetooth/bluetooth_low_energy_device_mac.mm +++ b/chromium/device/bluetooth/bluetooth_low_energy_device_mac.mm @@ -56,6 +56,8 @@ BluetoothLowEnergyDeviceMac::~BluetoothLowEnergyDeviceMac() { if (IsGattConnected()) { GetMacAdapter()->DisconnectGatt(this); } + + [peripheral_ setDelegate:nil]; } std::string BluetoothLowEnergyDeviceMac::GetIdentifier() const { diff --git a/chromium/device/bluetooth/bluetooth_low_energy_peripheral_delegate.mm b/chromium/device/bluetooth/bluetooth_low_energy_peripheral_delegate.mm index e0287fff8a6..f838aabe41e 100644 --- a/chromium/device/bluetooth/bluetooth_low_energy_peripheral_delegate.mm +++ b/chromium/device/bluetooth/bluetooth_low_energy_peripheral_delegate.mm @@ -75,11 +75,6 @@ class BluetoothLowEnergyPeripheralBridge { return self; } -- (void)dealloc { - [bridge_->GetPeripheral() setDelegate:nil]; - [super dealloc]; -} - - (void)peripheral:(CBPeripheral*)peripheral didModifyServices:(NSArray*)invalidatedServices { bridge_->DidModifyServices(invalidatedServices); diff --git a/chromium/device/bluetooth/bluetooth_low_energy_peripheral_manager_delegate.mm b/chromium/device/bluetooth/bluetooth_low_energy_peripheral_manager_delegate.mm index e1fe94fcc44..a951dfbf3b6 100644 --- a/chromium/device/bluetooth/bluetooth_low_energy_peripheral_manager_delegate.mm +++ b/chromium/device/bluetooth/bluetooth_low_energy_peripheral_manager_delegate.mm @@ -55,11 +55,6 @@ class BluetoothLowEnergyPeripheralManagerBridge { return self; } -- (void)dealloc { - [bridge_->GetPeripheralManager() setDelegate:nil]; - [super dealloc]; -} - - (void)peripheralManagerDidUpdateState:(CBPeripheralManager*)peripheral { bridge_->UpdatedState(); } diff --git a/chromium/device/bluetooth/bluetooth_pairing_winrt.cc b/chromium/device/bluetooth/bluetooth_pairing_winrt.cc new file mode 100644 index 00000000000..9f1ed08732d --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_pairing_winrt.cc @@ -0,0 +1,267 @@ +// Copyright 2018 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 "device/bluetooth/bluetooth_pairing_winrt.h" + +#include <utility> + +#include "base/logging.h" +#include "base/strings/string_piece.h" +#include "base/win/scoped_hstring.h" +#include "device/bluetooth/bluetooth_device_winrt.h" +#include "device/bluetooth/event_utils_winrt.h" + +namespace device { + +namespace { + +using ABI::Windows::Devices::Enumeration::DevicePairingKinds; +using ABI::Windows::Devices::Enumeration::DevicePairingKinds_ProvidePin; +using ABI::Windows::Devices::Enumeration::DevicePairingResult; +using ABI::Windows::Devices::Enumeration::DevicePairingResultStatus; +using ABI::Windows::Devices::Enumeration:: + DevicePairingResultStatus_AuthenticationFailure; +using ABI::Windows::Devices::Enumeration:: + DevicePairingResultStatus_AuthenticationTimeout; +using ABI::Windows::Devices::Enumeration:: + DevicePairingResultStatus_ConnectionRejected; +using ABI::Windows::Devices::Enumeration:: + DevicePairingResultStatus_AlreadyPaired; +using ABI::Windows::Devices::Enumeration::DevicePairingResultStatus_Failed; +using ABI::Windows::Devices::Enumeration:: + DevicePairingResultStatus_OperationAlreadyInProgress; +using ABI::Windows::Devices::Enumeration::DevicePairingResultStatus_Paired; +using ABI::Windows::Devices::Enumeration:: + DevicePairingResultStatus_PairingCanceled; +using ABI::Windows::Devices::Enumeration:: + DevicePairingResultStatus_RejectedByHandler; +using ABI::Windows::Devices::Enumeration::IDeviceInformationCustomPairing; +using ABI::Windows::Devices::Enumeration::IDevicePairingRequestedEventArgs; +using ABI::Windows::Devices::Enumeration::IDevicePairingResult; +using ABI::Windows::Foundation::IAsyncOperation; +using Microsoft::WRL::ComPtr; + +void PostTask(BluetoothPairingWinrt::ErrorCallback error_callback, + BluetoothDevice::ConnectErrorCode error_code) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(error_callback), error_code)); +} + +} // namespace + +BluetoothPairingWinrt::BluetoothPairingWinrt( + BluetoothDeviceWinrt* device, + BluetoothDevice::PairingDelegate* pairing_delegate, + ComPtr<IDeviceInformationCustomPairing> custom_pairing, + Callback callback, + ErrorCallback error_callback) + : device_(device), + pairing_delegate_(pairing_delegate), + custom_pairing_(std::move(custom_pairing)), + callback_(std::move(callback)), + error_callback_(std::move(error_callback)), + weak_ptr_factory_(this) { + DCHECK(device_); + DCHECK(pairing_delegate_); + DCHECK(custom_pairing_); +} + +BluetoothPairingWinrt::~BluetoothPairingWinrt() { + if (!pairing_requested_token_) + return; + + HRESULT hr = + custom_pairing_->remove_PairingRequested(*pairing_requested_token_); + if (FAILED(hr)) { + VLOG(2) << "Removing PairingRequested Handler failed: " + << logging::SystemErrorCodeToString(hr); + } +} + +void BluetoothPairingWinrt::StartPairing() { + pairing_requested_token_ = AddTypedEventHandler( + custom_pairing_.Get(), + &IDeviceInformationCustomPairing::add_PairingRequested, + base::BindRepeating(&BluetoothPairingWinrt::OnPairingRequested, + weak_ptr_factory_.GetWeakPtr())); + + if (!pairing_requested_token_) { + PostTask(std::move(error_callback_), + BluetoothDevice::ConnectErrorCode::ERROR_FAILED); + return; + } + + ComPtr<IAsyncOperation<DevicePairingResult*>> pair_op; + HRESULT hr = + custom_pairing_->PairAsync(DevicePairingKinds_ProvidePin, &pair_op); + if (FAILED(hr)) { + VLOG(2) << "DeviceInformationCustomPairing::PairAsync() failed: " + << logging::SystemErrorCodeToString(hr); + PostTask(std::move(error_callback_), + BluetoothDevice::ConnectErrorCode::ERROR_FAILED); + return; + } + + hr = PostAsyncResults(std::move(pair_op), + base::BindOnce(&BluetoothPairingWinrt::OnPair, + weak_ptr_factory_.GetWeakPtr())); + + if (FAILED(hr)) { + VLOG(2) << "PostAsyncResults failed: " + << logging::SystemErrorCodeToString(hr); + PostTask(std::move(error_callback_), + BluetoothDevice::ConnectErrorCode::ERROR_FAILED); + return; + } +} + +bool BluetoothPairingWinrt::ExpectingPinCode() const { + return expecting_pin_code_; +} + +void BluetoothPairingWinrt::SetPinCode(base::StringPiece pin_code) { + VLOG(2) << "BluetoothPairingWinrt::SetPinCode(" << pin_code << ")"; + auto pin_hstring = base::win::ScopedHString::Create(pin_code); + DCHECK(expecting_pin_code_); + expecting_pin_code_ = false; + DCHECK(pairing_requested_); + HRESULT hr = pairing_requested_->AcceptWithPin(pin_hstring.get()); + if (FAILED(hr)) { + VLOG(2) << "Accepting Pairing Request With Pin failed: " + << logging::SystemErrorCodeToString(hr); + std::move(error_callback_) + .Run(BluetoothDevice::ConnectErrorCode::ERROR_FAILED); + return; + } + + DCHECK(pairing_deferral_); + hr = pairing_deferral_->Complete(); + if (FAILED(hr)) { + VLOG(2) << "Completing Deferred Pairing Request failed: " + << logging::SystemErrorCodeToString(hr); + std::move(error_callback_) + .Run(BluetoothDevice::ConnectErrorCode::ERROR_FAILED); + } +} + +void BluetoothPairingWinrt::RejectPairing() { + VLOG(2) << "BluetoothPairingWinrt::RejectPairing()"; + DCHECK(pairing_deferral_); + HRESULT hr = pairing_deferral_->Complete(); + if (FAILED(hr)) { + VLOG(2) << "Completing Deferred Pairing Request failed: " + << logging::SystemErrorCodeToString(hr); + std::move(error_callback_) + .Run(BluetoothDevice::ConnectErrorCode::ERROR_FAILED); + return; + } + + std::move(error_callback_) + .Run(BluetoothDevice::ConnectErrorCode::ERROR_AUTH_REJECTED); +} + +void BluetoothPairingWinrt::CancelPairing() { + VLOG(2) << "BluetoothPairingWinrt::CancelPairing()"; + DCHECK(pairing_deferral_); + HRESULT hr = pairing_deferral_->Complete(); + if (FAILED(hr)) { + VLOG(2) << "Completing Deferred Pairing Request failed: " + << logging::SystemErrorCodeToString(hr); + std::move(error_callback_) + .Run(BluetoothDevice::ConnectErrorCode::ERROR_FAILED); + return; + } + + std::move(error_callback_) + .Run(BluetoothDevice::ConnectErrorCode::ERROR_AUTH_CANCELED); +} + +void BluetoothPairingWinrt::OnPairingRequested( + IDeviceInformationCustomPairing* custom_pairing, + IDevicePairingRequestedEventArgs* pairing_requested) { + VLOG(2) << "BluetoothPairingWinrt::OnPairingRequested()"; + + DevicePairingKinds pairing_kind; + HRESULT hr = pairing_requested->get_PairingKind(&pairing_kind); + if (FAILED(hr)) { + VLOG(2) << "Getting Pairing Kind failed: " + << logging::SystemErrorCodeToString(hr); + std::move(error_callback_) + .Run(BluetoothDevice::ConnectErrorCode::ERROR_FAILED); + return; + } + + VLOG(2) << "DevicePairingKind: " << static_cast<int>(pairing_kind); + if (pairing_kind != DevicePairingKinds_ProvidePin) { + VLOG(2) << "Unexpected DevicePairingKind."; + std::move(error_callback_) + .Run(BluetoothDevice::ConnectErrorCode::ERROR_FAILED); + return; + } + + hr = pairing_requested->GetDeferral(&pairing_deferral_); + if (FAILED(hr)) { + VLOG(2) << "Getting Pairing Deferral failed: " + << logging::SystemErrorCodeToString(hr); + std::move(error_callback_) + .Run(BluetoothDevice::ConnectErrorCode::ERROR_FAILED); + return; + } + + pairing_requested_ = pairing_requested; + expecting_pin_code_ = true; + pairing_delegate_->RequestPinCode(device_); +} + +void BluetoothPairingWinrt::OnPair( + ComPtr<IDevicePairingResult> pairing_result) { + DevicePairingResultStatus status; + HRESULT hr = pairing_result->get_Status(&status); + if (FAILED(hr)) { + VLOG(2) << "Getting Pairing Result Status failed: " + << logging::SystemErrorCodeToString(hr); + std::move(error_callback_) + .Run(BluetoothDevice::ConnectErrorCode::ERROR_FAILED); + return; + } + + VLOG(2) << "Pairing Result Status: " << static_cast<int>(status); + switch (status) { + case DevicePairingResultStatus_AlreadyPaired: + case DevicePairingResultStatus_Paired: + std::move(callback_).Run(); + return; + case DevicePairingResultStatus_PairingCanceled: + std::move(error_callback_) + .Run(BluetoothDevice::ConnectErrorCode::ERROR_AUTH_CANCELED); + return; + case DevicePairingResultStatus_AuthenticationFailure: + std::move(error_callback_) + .Run(BluetoothDevice::ConnectErrorCode::ERROR_AUTH_FAILED); + return; + case DevicePairingResultStatus_ConnectionRejected: + case DevicePairingResultStatus_RejectedByHandler: + std::move(error_callback_) + .Run(BluetoothDevice::ConnectErrorCode::ERROR_AUTH_REJECTED); + return; + case DevicePairingResultStatus_AuthenticationTimeout: + std::move(error_callback_) + .Run(BluetoothDevice::ConnectErrorCode::ERROR_AUTH_TIMEOUT); + return; + case DevicePairingResultStatus_Failed: + std::move(error_callback_) + .Run(BluetoothDevice::ConnectErrorCode::ERROR_FAILED); + return; + case DevicePairingResultStatus_OperationAlreadyInProgress: + std::move(error_callback_) + .Run(BluetoothDevice::ConnectErrorCode::ERROR_INPROGRESS); + return; + default: + std::move(error_callback_) + .Run(BluetoothDevice::ConnectErrorCode::ERROR_FAILED); + return; + } +} + +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_pairing_winrt.h b/chromium/device/bluetooth/bluetooth_pairing_winrt.h new file mode 100644 index 00000000000..88f57b6045f --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_pairing_winrt.h @@ -0,0 +1,99 @@ +// Copyright 2018 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. + +#ifndef DEVICE_BLUETOOTH_BLUETOOTH_PAIRING_WINRT_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_PAIRING_WINRT_H_ + +#include <windows.devices.enumeration.h> +#include <windows.foundation.h> +#include <wrl/client.h> + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/strings/string_piece_forward.h" +#include "device/bluetooth/bluetooth_device.h" + +namespace device { + +class BluetoothDeviceWinrt; + +// This class encapsulates logic required to perform a custom pairing on WinRT. +// Currently only pairing with a pin code is supported. +class BluetoothPairingWinrt { + public: + using Callback = base::OnceClosure; + using ErrorCallback = + base::OnceCallback<void(BluetoothDevice::ConnectErrorCode)>; + + BluetoothPairingWinrt( + BluetoothDeviceWinrt* device, + BluetoothDevice::PairingDelegate* pairing_delegate, + Microsoft::WRL::ComPtr< + ABI::Windows::Devices::Enumeration::IDeviceInformationCustomPairing> + custom_pairing, + Callback callback, + ErrorCallback error_callback); + + ~BluetoothPairingWinrt(); + + // Initiates the pairing procedure. + void StartPairing(); + + // Indicates whether the device is currently pairing and expecting a + // PIN Code to be returned. + bool ExpectingPinCode() const; + + // Sends the PIN code |pin_code| to the remote device during pairing. + void SetPinCode(base::StringPiece pin_code); + + // Rejects a pairing or connection request from a remote device. + void RejectPairing(); + + // Cancels a pairing or connection attempt to a remote device. + void CancelPairing(); + + private: + void OnPairingRequested( + ABI::Windows::Devices::Enumeration::IDeviceInformationCustomPairing* + custom_pairing, + ABI::Windows::Devices::Enumeration::IDevicePairingRequestedEventArgs* + pairing_requested); + + void OnPair(Microsoft::WRL::ComPtr< + ABI::Windows::Devices::Enumeration::IDevicePairingResult> + pairing_result); + + // Weak. This is the device object that owns this pairing instance. + BluetoothDeviceWinrt* device_; + + // Weak. This is the pairing delegate provided to BluetoothDevice::Pair. + // Clients need to ensure the delegate stays alive during the pairing + // procedure. + BluetoothDevice::PairingDelegate* pairing_delegate_; + + // Boolean indicating whether the device is currently pairing and expecting a + // PIN Code to be returned. + bool expecting_pin_code_ = false; + + Microsoft::WRL::ComPtr< + ABI::Windows::Devices::Enumeration::IDeviceInformationCustomPairing> + custom_pairing_; + Callback callback_; + ErrorCallback error_callback_; + + base::Optional<EventRegistrationToken> pairing_requested_token_; + + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IDeferral> pairing_deferral_; + Microsoft::WRL::ComPtr< + ABI::Windows::Devices::Enumeration::IDevicePairingRequestedEventArgs> + pairing_requested_; + + base::WeakPtrFactory<BluetoothPairingWinrt> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothPairingWinrt); +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_PAIRING_WINRT_H_ diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic.h b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic.h index c475063f5eb..c3c8df5d4f9 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic.h +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic.h @@ -169,6 +169,10 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothRemoteGattCharacteristic virtual bool WriteWithoutResponse(base::span<const uint8_t> value); protected: + using DescriptorMap = + base::flat_map<std::string, + std::unique_ptr<BluetoothRemoteGattDescriptor>>; + BluetoothRemoteGattCharacteristic(); // Writes to the Client Characteristic Configuration descriptor to enable @@ -206,8 +210,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothRemoteGattCharacteristic // Descriptors owned by the chracteristic. The descriptors' identifiers serve // as keys. - base::flat_map<std::string, std::unique_ptr<BluetoothRemoteGattDescriptor>> - descriptors_; + DescriptorMap descriptors_; private: friend class BluetoothGattNotifySession; diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm index 2e91b58f8c6..086b01e6dba 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm @@ -84,14 +84,12 @@ BluetoothRemoteGattCharacteristicMac::BluetoothRemoteGattCharacteristicMac( BluetoothRemoteGattCharacteristicMac::~BluetoothRemoteGattCharacteristicMac() { if (HasPendingRead()) { - std::pair<ValueCallback, ErrorCallback> callbacks; - callbacks.swap(read_characteristic_value_callbacks_); - callbacks.second.Run(BluetoothGattService::GATT_ERROR_FAILED); + read_characteristic_value_callbacks_.second.Run( + BluetoothGattService::GATT_ERROR_FAILED); } if (HasPendingWrite()) { - std::pair<base::Closure, ErrorCallback> callbacks; - callbacks.swap(write_characteristic_value_callbacks_); - callbacks.second.Run(BluetoothGattService::GATT_ERROR_FAILED); + write_characteristic_value_callbacks_.second.Run( + BluetoothGattService::GATT_ERROR_FAILED); } } diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc index c642513aa07..e6c286b2d5e 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc @@ -29,7 +29,12 @@ namespace device { -class BluetoothRemoteGattCharacteristicTest : public BluetoothTest { +class BluetoothRemoteGattCharacteristicTest : +#if defined(OS_WIN) + public BluetoothTestWinrt { +#else + public BluetoothTest { +#endif public: // Creates adapter_, device_, service_, characteristic1_, & characteristic2_. // |properties| will be used for each characteristic. @@ -49,6 +54,7 @@ class BluetoothRemoteGattCharacteristicTest : public BluetoothTest { service_ = device_->GetGattServices()[0]; SimulateGattCharacteristic(service_, kTestUUIDDeviceName, properties); SimulateGattCharacteristic(service_, kTestUUIDDeviceName, properties); + base::RunLoop().RunUntilIdle(); ASSERT_EQ(2u, service_->GetCharacteristics().size()); characteristic1_ = service_->GetCharacteristics()[0]; characteristic2_ = service_->GetCharacteristics()[1]; @@ -91,6 +97,7 @@ class BluetoothRemoteGattCharacteristicTest : public BluetoothTest { .canonical_value()); expected_descriptors_count++; } + base::RunLoop().RunUntilIdle(); ASSERT_EQ(expected_descriptors_count, characteristic1_->GetDescriptors().size()); @@ -131,18 +138,42 @@ class BluetoothRemoteGattCharacteristicTest : public BluetoothTest { ExpectedNotifyValue(notify_value_state); } + // A few tests below don't behave correctly on Classic Windows, but do for + // WinRT. Since a #if defined(OS_WIN) guard is not sufficient to distinguish + // these two cases, this small utility function is added. + bool IsClassicWin() { +#if defined(OS_WIN) + return !GetParam(); +#else + return false; +#endif + } + BluetoothDevice* device_ = nullptr; BluetoothRemoteGattService* service_ = nullptr; BluetoothRemoteGattCharacteristic* characteristic1_ = nullptr; BluetoothRemoteGattCharacteristic* characteristic2_ = nullptr; }; -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_WIN) +using BluetoothRemoteGattCharacteristicTestWinrt = + BluetoothRemoteGattCharacteristicTest; +using BluetoothRemoteGattCharacteristicTestWin32Only = + BluetoothRemoteGattCharacteristicTest; +using BluetoothRemoteGattCharacteristicTestWinrtOnly = + BluetoothRemoteGattCharacteristicTest; +#endif + +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_GetIdentifier GetIdentifier #else #define MAYBE_GetIdentifier DISABLED_GetIdentifier #endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, GetIdentifier) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GetIdentifier) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -181,6 +212,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GetIdentifier) { SimulateGattCharacteristic(service2, kTestUUIDDeviceName, /* properties */ 0); SimulateGattCharacteristic(service3, kTestUUIDDeviceName, /* properties */ 0); SimulateGattCharacteristic(service3, kTestUUIDDeviceName, /* properties */ 0); + base::RunLoop().RunUntilIdle(); BluetoothRemoteGattCharacteristic* char1 = service1->GetCharacteristics()[0]; BluetoothRemoteGattCharacteristic* char2 = service1->GetCharacteristics()[1]; BluetoothRemoteGattCharacteristic* char3 = service2->GetCharacteristics()[0]; @@ -210,12 +242,16 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GetIdentifier) { EXPECT_NE(char5->GetIdentifier(), char6->GetIdentifier()); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_GetUUID GetUUID #else #define MAYBE_GetUUID DISABLED_GetUUID #endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, GetUUID) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GetUUID) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -238,6 +274,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GetUUID) { SimulateGattCharacteristic(service, kTestUUIDDeviceName, /* properties */ 0); SimulateGattCharacteristic(service, kTestUUIDAppearance, /* properties */ 0); SimulateGattCharacteristic(service, kTestUUIDAppearance, /* properties */ 0); + base::RunLoop().RunUntilIdle(); BluetoothRemoteGattCharacteristic* char1 = service->GetCharacteristics()[0]; BluetoothRemoteGattCharacteristic* char2 = service->GetCharacteristics()[1]; BluetoothRemoteGattCharacteristic* char3 = service->GetCharacteristics()[2]; @@ -254,12 +291,16 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GetUUID) { EXPECT_EQ(uuid2, char3->GetUUID()); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_GetProperties GetProperties #else #define MAYBE_GetProperties DISABLED_GetProperties #endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, GetProperties) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GetProperties) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -279,6 +320,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GetProperties) { // Create two characteristics with different properties: SimulateGattCharacteristic(service, kTestUUIDDeviceName, /* properties */ 0); SimulateGattCharacteristic(service, kTestUUIDDeviceName, /* properties */ 7); + base::RunLoop().RunUntilIdle(); // Read the properties. Because ordering is unknown swap as necessary. int properties1 = service->GetCharacteristics()[0]->GetProperties(); @@ -289,13 +331,17 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GetProperties) { EXPECT_EQ(7, properties2); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_GetService GetService #else #define MAYBE_GetService DISABLED_GetService #endif // Tests GetService. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, GetService) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GetService) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -306,15 +352,20 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GetService) { EXPECT_EQ(service_, characteristic2_->GetService()); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_ReadRemoteCharacteristic_Empty ReadRemoteCharacteristic_Empty #else #define MAYBE_ReadRemoteCharacteristic_Empty \ DISABLED_ReadRemoteCharacteristic_Empty #endif // Tests ReadRemoteCharacteristic and GetValue with empty value buffer. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + ReadRemoteCharacteristic_Empty) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_ReadRemoteCharacteristic_Empty) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -338,15 +389,20 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, EXPECT_EQ(empty_vector, characteristic1_->GetValue()); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_WriteRemoteCharacteristic_Empty WriteRemoteCharacteristic_Empty #else #define MAYBE_WriteRemoteCharacteristic_Empty \ DISABLED_WriteRemoteCharacteristic_Empty #endif // Tests WriteRemoteCharacteristic with empty value buffer. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + WriteRemoteCharacteristic_Empty) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteRemoteCharacteristic_Empty) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -369,7 +425,91 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, EXPECT_EQ(empty_vector, last_write_value_); } -#if defined(OS_ANDROID) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) +#define MAYBE_Retry_ReadRemoteCharacteristic_DuringDestruction_Fails \ + Retry_ReadRemoteCharacteristic_DuringDestruction_Fails +#else +#define MAYBE_Retry_ReadRemoteCharacteristic_DuringDestruction_Fails \ + DISABLED_Retry_ReadRemoteCharacteristic_DuringDestruction_Fails +#endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + Retry_ReadRemoteCharacteristic_DuringDestruction_Fails) { +#else +TEST_F(BluetoothRemoteGattCharacteristicTest, + MAYBE_Retry_ReadRemoteCharacteristic_DuringDestruction_Fails) { +#endif + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( + BluetoothRemoteGattCharacteristic::PROPERTY_READ)); + + bool read_error_callback_called = false; + characteristic1_->ReadRemoteCharacteristic( + GetReadValueCallback(Call::NOT_EXPECTED), + base::BindLambdaForTesting( + [&](BluetoothRemoteGattService::GattErrorCode error_code) { + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED, + error_code); + read_error_callback_called = true; + // Retrying Read should fail: + characteristic1_->ReadRemoteCharacteristic( + GetReadValueCallback(Call::NOT_EXPECTED), + GetGattErrorCallback(Call::EXPECTED)); + })); + + DeleteDevice(device_); // TODO(576906) delete only the characteristic. + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(read_error_callback_called); + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_IN_PROGRESS, + last_gatt_error_code_); +} + +#if defined(OS_ANDROID) || defined(OS_MACOSX) +#define MAYBE_Retry_WriteRemoteCharacteristic_DuringDestruction_Fails \ + Retry_WriteRemoteCharacteristic_DuringDestruction_Fails +#else +#define MAYBE_Retry_WriteRemoteCharacteristic_DuringDestruction_Fails \ + DISABLED_Retry_WriteRemoteCharacteristic_DuringDestruction_Fails +#endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + Retry_WriteRemoteCharacteristic_DuringDestruction_Fails) { +#else +TEST_F(BluetoothRemoteGattCharacteristicTest, + MAYBE_Retry_WriteRemoteCharacteristic_DuringDestruction_Fails) { +#endif + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( + BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + + bool write_error_callback_called = false; + characteristic1_->WriteRemoteCharacteristic( + {} /* value */, GetCallback(Call::NOT_EXPECTED), + base::BindLambdaForTesting( + [&](BluetoothRemoteGattService::GattErrorCode error_code) { + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED, + error_code); + write_error_callback_called = true; + // Retrying Write should fail: + characteristic1_->WriteRemoteCharacteristic( + {} /* value */, GetCallback(Call::NOT_EXPECTED), + GetGattErrorCallback(Call::EXPECTED)); + })); + + DeleteDevice(device_); // TODO(576906) delete only the characteristic. + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(write_error_callback_called); + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_IN_PROGRESS, + last_gatt_error_code_); +} + +#if defined(OS_ANDROID) #define MAYBE_ReadRemoteCharacteristic_AfterDeleted \ ReadRemoteCharacteristic_AfterDeleted #else @@ -379,8 +519,15 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, // Tests ReadRemoteCharacteristic completing after Chrome objects are deleted. // macOS: Not applicable: This can never happen if CBPeripheral delegate is set // to nil. +// WinRT: Not applicable: Pending callbacks won't fire once the underlying +// object is destroyed. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWin32Only, + ReadRemoteCharacteristic_AfterDeleted) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_ReadRemoteCharacteristic_AfterDeleted) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -411,8 +558,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #define MAYBE_ReadRemoteCharacteristic_Disconnected \ DISABLED_ReadRemoteCharacteristic_Disconnected #endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + ReadRemoteCharacteristic_Disconnected) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_ReadRemoteCharacteristic_Disconnected) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -425,23 +577,23 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, GetGattErrorCallback(Call::EXPECTED)); // Set up for receiving a read response after disconnection. -// On macOS no events arrive after disconnection so there is no point +// On macOS or WinRT no events arrive after disconnection so there is no point // in building the infrastructure to test this behavior. FYI // the code CHECKs that responses arrive only when the device is connected. -#if !defined(OS_MACOSX) +#if defined(OS_ANDROID) RememberCharacteristicForSubsequentAction(characteristic1_); #endif ASSERT_EQ(1u, adapter_->GetDevices().size()); - SimulateGattDisconnection(adapter_->GetDevices()[0]); + SimulateDeviceBreaksConnection(adapter_->GetDevices()[0]); base::RunLoop().RunUntilIdle(); EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED, last_gatt_error_code_); // Dispatch read response after disconnection. See above explanation for why -// we don't do this in macOS. -#if !defined(OS_MACOSX) +// we don't do this in macOS on WinRT. +#if defined(OS_ANDROID) std::vector<uint8_t> empty_vector; SimulateGattCharacteristicRead(nullptr /* use remembered characteristic */, empty_vector); @@ -449,7 +601,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #endif } -#if defined(OS_ANDROID) || defined(OS_WIN) +#if defined(OS_ANDROID) #define MAYBE_WriteRemoteCharacteristic_AfterDeleted \ WriteRemoteCharacteristic_AfterDeleted #else @@ -459,8 +611,15 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, // Tests WriteRemoteCharacteristic completing after Chrome objects are deleted. // macOS: Not applicable: This can never happen if CBPeripheral delegate is set // to nil. +// WinRT: Not applicable: Pending callbacks won't fire once the underlying +// object is destroyed. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWin32Only, + WriteRemoteCharacteristic_AfterDeleted) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteRemoteCharacteristic_AfterDeleted) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -490,8 +649,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #define MAYBE_WriteRemoteCharacteristic_Disconnected \ DISABLED_WriteRemoteCharacteristic_Disconnected #endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + WriteRemoteCharacteristic_Disconnected) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteRemoteCharacteristic_Disconnected) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -505,35 +669,39 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, GetGattErrorCallback(Call::EXPECTED)); // Set up for receiving a write response after disconnection. -// On macOS no events arrive after disconnection so there is no point +// On macOS and WinRT no events arrive after disconnection so there is no point // in building the infrastructure to test this behavior. FYI // the code CHECKs that responses arrive only when the device is connected. -#if !defined(OS_MACOSX) +#if defined(OS_ANDROID) RememberCharacteristicForSubsequentAction(characteristic1_); -#endif // !defined(OS_MACOSX) +#endif // defined(OS_ANDROID) ASSERT_EQ(1u, adapter_->GetDevices().size()); - SimulateGattDisconnection(adapter_->GetDevices()[0]); + SimulateDeviceBreaksConnection(adapter_->GetDevices()[0]); base::RunLoop().RunUntilIdle(); EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED, last_gatt_error_code_); // Dispatch write response after disconnection. See above explanation for why -// we don't do this in macOS. -#if !defined(OS_MACOSX) +// we don't do this in macOS and WinRT. +#if defined(OS_ANDROID) SimulateGattCharacteristicWrite(/* use remembered characteristic */ nullptr); base::RunLoop().RunUntilIdle(); -#endif // !defined(OS_MACOSX) +#endif // defined(OS_ANDROID) } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_ReadRemoteCharacteristic ReadRemoteCharacteristic #else #define MAYBE_ReadRemoteCharacteristic DISABLED_ReadRemoteCharacteristic #endif // Tests ReadRemoteCharacteristic and GetValue with non-empty value buffer. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, ReadRemoteCharacteristic) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_ReadRemoteCharacteristic) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -569,7 +737,7 @@ static void test_callback( callback.Run(value); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_ReadRemoteCharacteristic_GattCharacteristicValueChangedNotCalled \ ReadRemoteCharacteristic_GattCharacteristicValueChangedNotCalled #else @@ -578,8 +746,13 @@ static void test_callback( #endif // Tests that ReadRemoteCharacteristic doesn't result in a // GattCharacteristicValueChanged call. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + ReadRemoteCharacteristic_GattCharacteristicValueChangedNotCalled) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_ReadRemoteCharacteristic_GattCharacteristicValueChangedNotCalled) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -601,23 +774,27 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count()); // TODO(https://crbug.com/699694): Remove this #if once the bug on Windows is // fixed. -#if defined(OS_WIN) - EXPECT_FALSE(observer.last_gatt_characteristic_id().empty()); - EXPECT_TRUE(observer.last_gatt_characteristic_uuid().IsValid()); -#else - EXPECT_TRUE(observer.last_gatt_characteristic_id().empty()); - EXPECT_FALSE(observer.last_gatt_characteristic_uuid().IsValid()); -#endif // defined(OS_WIN) + if (IsClassicWin()) { + EXPECT_FALSE(observer.last_gatt_characteristic_id().empty()); + EXPECT_TRUE(observer.last_gatt_characteristic_uuid().IsValid()); + } else { + EXPECT_TRUE(observer.last_gatt_characteristic_id().empty()); + EXPECT_FALSE(observer.last_gatt_characteristic_uuid().IsValid()); + } EXPECT_TRUE(observer.last_changed_characteristic_value().empty()); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_WriteRemoteCharacteristic WriteRemoteCharacteristic #else #define MAYBE_WriteRemoteCharacteristic DISABLED_WriteRemoteCharacteristic #endif // Tests WriteRemoteCharacteristic with non-empty value buffer. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, WriteRemoteCharacteristic) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteRemoteCharacteristic) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -637,22 +814,26 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteRemoteCharacteristic) { base::RunLoop().RunUntilIdle(); EXPECT_EQ(1, gatt_write_characteristic_attempts_); -#if !defined(OS_WIN) - // TODO(crbug.com/653291): remove this #if once the bug on windows is fixed. - EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count()); -#endif + // TODO(crbug.com/653291): remove this if once the bug on windows is fixed. + if (!IsClassicWin()) + EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count()); EXPECT_EQ(test_vector, last_write_value_); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_ReadRemoteCharacteristic_Twice ReadRemoteCharacteristic_Twice #else #define MAYBE_ReadRemoteCharacteristic_Twice \ DISABLED_ReadRemoteCharacteristic_Twice #endif // Tests ReadRemoteCharacteristic and GetValue multiple times. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + ReadRemoteCharacteristic_Twice) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_ReadRemoteCharacteristic_Twice) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -689,15 +870,20 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, EXPECT_EQ(empty_vector, characteristic1_->GetValue()); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_WriteRemoteCharacteristic_Twice WriteRemoteCharacteristic_Twice #else #define MAYBE_WriteRemoteCharacteristic_Twice \ DISABLEDWriteRemoteCharacteristic_Twice #endif // Tests WriteRemoteCharacteristic multiple times. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + WriteRemoteCharacteristic_Twice) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteRemoteCharacteristic_Twice) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -733,7 +919,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, EXPECT_EQ(empty_vector, last_write_value_); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_ReadRemoteCharacteristic_MultipleCharacteristics \ ReadRemoteCharacteristic_MultipleCharacteristics #else @@ -741,8 +927,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, DISABLED_ReadRemoteCharacteristic_MultipleCharacteristics #endif // Tests ReadRemoteCharacteristic on two characteristics. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + ReadRemoteCharacteristic_MultipleCharacteristics) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_ReadRemoteCharacteristic_MultipleCharacteristics) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -778,7 +969,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, EXPECT_EQ(test_vector2, characteristic2_->GetValue()); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_WriteRemoteCharacteristic_MultipleCharacteristics \ WriteRemoteCharacteristic_MultipleCharacteristics #else @@ -786,8 +977,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, DISABLED_WriteRemoteCharacteristic_MultipleCharacteristics #endif // Tests WriteRemoteCharacteristic on two characteristics. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + WriteRemoteCharacteristic_MultipleCharacteristics) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteRemoteCharacteristic_MultipleCharacteristics) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -800,33 +996,29 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, characteristic1_->WriteRemoteCharacteristic( test_vector1, GetCallback(Call::EXPECTED), GetGattErrorCallback(Call::NOT_EXPECTED)); -#if defined(OS_ANDROID) || defined(OS_MACOSX) - EXPECT_EQ(test_vector1, last_write_value_); -#endif + if (!IsClassicWin()) + EXPECT_EQ(test_vector1, last_write_value_); std::vector<uint8_t> test_vector2; test_vector2.push_back(222); characteristic2_->WriteRemoteCharacteristic( test_vector2, GetCallback(Call::EXPECTED), GetGattErrorCallback(Call::NOT_EXPECTED)); -#if defined(OS_ANDROID) || defined(OS_MACOSX) - EXPECT_EQ(test_vector2, last_write_value_); -#endif + if (!IsClassicWin()) + EXPECT_EQ(test_vector2, last_write_value_); EXPECT_EQ(0, callback_count_); EXPECT_EQ(0, error_callback_count_); SimulateGattCharacteristicWrite(characteristic1_); base::RunLoop().RunUntilIdle(); -#if !(defined(OS_ANDROID) || defined(OS_MACOSX)) - EXPECT_EQ(test_vector1, last_write_value_); -#endif + if (IsClassicWin()) + EXPECT_EQ(test_vector1, last_write_value_); SimulateGattCharacteristicWrite(characteristic2_); base::RunLoop().RunUntilIdle(); -#if !(defined(OS_ANDROID) || defined(OS_MACOSX)) - EXPECT_EQ(test_vector2, last_write_value_); -#endif + if (IsClassicWin()) + EXPECT_EQ(test_vector2, last_write_value_); EXPECT_EQ(2, gatt_write_characteristic_attempts_); EXPECT_EQ(2, callback_count_); @@ -835,7 +1027,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, // TODO(591740): Remove if define for OS_ANDROID in this test. } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_RemoteCharacteristic_Nested_Read_Read \ RemoteCharacteristic_Nested_Read_Read #else @@ -844,8 +1036,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #endif // Tests a nested ReadRemoteCharacteristic from within another // ReadRemoteCharacteristic. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + RemoteCharacteristic_Nested_Read_Read) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_RemoteCharacteristic_Nested_Read_Read) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -882,7 +1079,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, EXPECT_EQ(test_vector_2, characteristic1_->GetValue()); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_RemoteCharacteristic_Nested_Write_Write \ RemoteCharacteristic_Nested_Write_Write #else @@ -891,8 +1088,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #endif // Tests a nested WriteRemoteCharacteristic from within another // WriteRemoteCharacteristic. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + RemoteCharacteristic_Nested_Write_Write) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_RemoteCharacteristic_Nested_Write_Write) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -928,7 +1130,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, EXPECT_EQ(test_vector_2, last_write_value_); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_RemoteCharacteristic_Nested_Read_Write \ RemoteCharacteristic_Nested_Read_Write #else @@ -937,8 +1139,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #endif // Tests a nested WriteRemoteCharacteristic from within a // ReadRemoteCharacteristic. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + RemoteCharacteristic_Nested_Read_Write) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_RemoteCharacteristic_Nested_Read_Write) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -977,7 +1184,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, EXPECT_EQ(test_vector_2, last_write_value_); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_RemoteCharacteristic_Nested_Write_Read \ RemoteCharacteristic_Nested_Write_Read #else @@ -986,8 +1193,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #endif // Tests a nested ReadRemoteCharacteristic from within a // WriteRemoteCharacteristic. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + RemoteCharacteristic_Nested_Write_Read) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_RemoteCharacteristic_Nested_Write_Read) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1026,13 +1238,17 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, EXPECT_EQ(test_vector_2, characteristic1_->GetValue()); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_ReadError ReadError #else #define MAYBE_ReadError DISABLED_ReadError #endif // Tests ReadRemoteCharacteristic asynchronous error. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, ReadError) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_ReadError) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1055,13 +1271,17 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_ReadError) { EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count()); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_WriteError WriteError #else #define MAYBE_WriteError DISABLED_WriteError #endif // Tests WriteRemoteCharacteristic asynchronous error. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, WriteError) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteError) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1089,8 +1309,8 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteError) { #define MAYBE_ReadSynchronousError DISABLED_ReadSynchronousError #endif // Tests ReadRemoteCharacteristic synchronous error. -// Test not relevant for macOS since characteristic read cannot generate -// synchronous error. +// Test not relevant for macOS and WinRT since characteristic read cannot +// generate synchronous error. TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_ReadSynchronousError) { ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate()); @@ -1124,7 +1344,8 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_ReadSynchronousError) { #define MAYBE_WriteSynchronousError DISABLED_WriteSynchronousError #endif // Tests WriteRemoteCharacteristic synchronous error. -// This test doesn't apply to macOS synchronous API does exist. +// This test doesn't apply to macOS and WinRT since a synchronous API does not +// exist. TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteSynchronousError) { ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate()); @@ -1152,7 +1373,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteSynchronousError) { EXPECT_EQ(0, error_callback_count_); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_ReadRemoteCharacteristic_ReadPending \ ReadRemoteCharacteristic_ReadPending #else @@ -1160,8 +1381,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteSynchronousError) { DISABLED_ReadRemoteCharacteristic_ReadPending #endif // Tests ReadRemoteCharacteristic error with a pending read operation. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + ReadRemoteCharacteristic_ReadPending) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_ReadRemoteCharacteristic_ReadPending) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1192,7 +1418,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, EXPECT_EQ(0, error_callback_count_); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_WriteRemoteCharacteristic_WritePending \ WriteRemoteCharacteristic_WritePending #else @@ -1200,8 +1426,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, DISABLED_WriteRemoteCharacteristic_WritePending #endif // Tests WriteRemoteCharacteristic error with a pending write operation. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + WriteRemoteCharacteristic_WritePending) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteRemoteCharacteristic_WritePending) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1232,7 +1463,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, EXPECT_EQ(0, error_callback_count_); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_ReadRemoteCharacteristic_WritePending \ ReadRemoteCharacteristic_WritePending #else @@ -1240,8 +1471,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, DISABLED_ReadRemoteCharacteristic_WritePending #endif // Tests ReadRemoteCharacteristic error with a pending write operation. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + ReadRemoteCharacteristic_WritePending) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_ReadRemoteCharacteristic_WritePending) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1273,7 +1509,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, EXPECT_EQ(0, error_callback_count_); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_WriteRemoteCharacteristic_ReadPending \ WriteRemoteCharacteristic_ReadPending #else @@ -1281,8 +1517,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, DISABLED_WriteRemoteCharacteristic_ReadPending #endif // Tests WriteRemoteCharacteristic error with a pending Read operation. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + WriteRemoteCharacteristic_ReadPending) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteRemoteCharacteristic_ReadPending) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1324,8 +1565,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, // how other platforms set global variables. // Tests that a notification arriving during a pending read doesn't // cause a crash. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + Notification_During_ReadRemoteCharacteristic) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_Notification_During_ReadRemoteCharacteristic) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1375,7 +1621,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #endif // defined(OS_MACOSX) } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_Notification_During_WriteRemoteCharacteristic \ Notification_During_WriteRemoteCharacteristic #else @@ -1384,8 +1630,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #endif // Tests that a notification arriving during a pending write doesn't // cause a crash. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + Notification_During_WriteRemoteCharacteristic) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_Notification_During_WriteRemoteCharacteristic) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1416,7 +1667,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, EXPECT_EQ(write_value, last_write_value_); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_StartNotifySession_NoNotifyOrIndicate \ StartNotifySession_NoNotifyOrIndicate #else @@ -1425,8 +1676,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #endif // StartNotifySession fails if characteristic doesn't have Notify or Indicate // property. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + StartNotifySession_NoNotifyOrIndicate) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StartNotifySession_NoNotifyOrIndicate) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1445,7 +1701,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, last_gatt_error_code_); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_StartNotifySession_NoConfigDescriptor \ StartNotifySession_NoConfigDescriptor #else @@ -1454,8 +1710,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #endif // StartNotifySession fails if the characteristic is missing the Client // Characteristic Configuration descriptor. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + StartNotifySession_NoConfigDescriptor) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StartNotifySession_NoConfigDescriptor) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1474,7 +1735,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, last_gatt_error_code_); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_StartNotifySession_MultipleConfigDescriptor \ StartNotifySession_MultipleConfigDescriptor #else @@ -1483,8 +1744,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #endif // StartNotifySession fails if the characteristic has multiple Client // Characteristic Configuration descriptors. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + StartNotifySession_MultipleConfigDescriptor) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StartNotifySession_MultipleConfigDescriptor) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1560,13 +1826,17 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, ASSERT_EQ(0u, notify_sessions_.size()); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_StartNotifySession StartNotifySession #else #define MAYBE_StartNotifySession DISABLED_StartNotifySession #endif // Tests StartNotifySession success on a characteristic that enabled Notify. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, StartNotifySession) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StartNotifySession) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1575,15 +1845,20 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StartNotifySession) { /* properties: NOTIFY */ 0x10, NotifyValueState::NOTIFY)); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_StartNotifySession_OnIndicate StartNotifySession_OnIndicate #else #define MAYBE_StartNotifySession_OnIndicate \ DISABLED_StartNotifySession_OnIndicate #endif // Tests StartNotifySession success on a characteristic that enabled Indicate. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + StartNotifySession_OnIndicate) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StartNotifySession_OnIndicate) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1592,7 +1867,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, /* properties: INDICATE */ 0x20, NotifyValueState::INDICATE)); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_StartNotifySession_OnNotifyAndIndicate \ StartNotifySession_OnNotifyAndIndicate #else @@ -1601,8 +1876,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #endif // Tests StartNotifySession success on a characteristic that enabled Notify & // Indicate. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + StartNotifySession_OnNotifyAndIndicate) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StartNotifySession_OnNotifyAndIndicate) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1612,14 +1892,19 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, NotifyValueState::NOTIFY)); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_StartNotifySession_Multiple StartNotifySession_Multiple #else #define MAYBE_StartNotifySession_Multiple DISABLED_StartNotifySession_Multiple #endif // Tests multiple StartNotifySession success. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + StartNotifySession_Multiple) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StartNotifySession_Multiple) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1630,6 +1915,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, characteristic1_, BluetoothRemoteGattDescriptor::ClientCharacteristicConfigurationUuid() .canonical_value()); + base::RunLoop().RunUntilIdle(); ASSERT_EQ(1u, characteristic1_->GetDescriptors().size()); characteristic1_->StartNotifySession( @@ -1663,8 +1949,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, DISABLED_StartNotifySessionError_Multiple #endif // Tests multiple StartNotifySessions pending and then an error. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + StartNotifySessionError_Multiple) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StartNotifySessionError_Multiple) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1675,6 +1966,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, characteristic1_, BluetoothRemoteGattDescriptor::ClientCharacteristicConfigurationUuid() .canonical_value()); + base::RunLoop().RunUntilIdle(); ASSERT_EQ(1u, characteristic1_->GetDescriptors().size()); characteristic1_->StartNotifySession(GetNotifyCallback(Call::NOT_EXPECTED), @@ -1739,8 +2031,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, DISABLED_StartNotifySession_BeforeDeleted #endif // Tests StartNotifySession completing before chrome objects are deleted. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + StartNotifySession_BeforeDeleted) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StartNotifySession_BeforeDeleted) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1751,6 +2048,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, characteristic1_, BluetoothRemoteGattDescriptor::ClientCharacteristicConfigurationUuid() .canonical_value()); + base::RunLoop().RunUntilIdle(); ASSERT_EQ(1u, characteristic1_->GetDescriptors().size()); characteristic1_->StartNotifySession( @@ -1781,7 +2079,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, EXPECT_FALSE(notify_sessions_[0]->IsActive()); } -#if defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_MACOSX) #define MAYBE_StartNotifySession_Reentrant_Success_Success \ StartNotifySession_Reentrant_Success_Success #else @@ -1790,8 +2088,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #endif // Tests StartNotifySession reentrant in start notify session success callback // and the reentrant start notify session success. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + StartNotifySession_Reentrant_Success_Success) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StartNotifySession_Reentrant_Success_Success) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1800,7 +2103,9 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, FakeCharacteristicBoilerplate(/* properties: NOTIFY */ 0x10)); SimulateGattDescriptor( characteristic1_, - BluetoothGattDescriptor::ClientCharacteristicConfigurationUuid().value()); + BluetoothGattDescriptor::ClientCharacteristicConfigurationUuid() + .canonical_value()); + base::RunLoop().RunUntilIdle(); ASSERT_EQ(1u, characteristic1_->GetDescriptors().size()); characteristic1_->StartNotifySession( @@ -1831,16 +2136,10 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, } #if defined(OS_WIN) -#define MAYBE_StartNotifySession_Reentrant_Error_Error \ - StartNotifySession_Reentrant_Error_Error -#else -#define MAYBE_StartNotifySession_Reentrant_Error_Error \ - DISABLED_StartNotifySession_Reentrant_Error_Error -#endif // Tests StartNotifySession reentrant in start notify session error callback // and the reentrant start notify session error. -TEST_F(BluetoothRemoteGattCharacteristicTest, - MAYBE_StartNotifySession_Reentrant_Error_Error) { +TEST_P(BluetoothRemoteGattCharacteristicTestWin32Only, + StartNotifySession_Reentrant_Error_Error) { ASSERT_NO_FATAL_FAILURE( FakeCharacteristicBoilerplate(/* properties: NOTIFY */ 0x10)); SimulateGattDescriptor( @@ -1870,6 +2169,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, EXPECT_EQ(2, error_callback_count_); ASSERT_EQ(0u, notify_sessions_.size()); } +#endif #if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_StopNotifySession StopNotifySession @@ -1877,7 +2177,11 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #define MAYBE_StopNotifySession DISABLED_StopNotifySession #endif // Tests StopNotifySession success on a characteristic that enabled Notify. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, StopNotifySession) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StopNotifySession) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1906,8 +2210,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StopNotifySession) { DISABLED_StopNotifySession_SessionDeleted #endif // Tests that deleted sessions are stopped. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + StopNotifySession_SessionDeleted) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StopNotifySession_SessionDeleted) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1937,8 +2246,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #endif // Tests that deleting the sessions before the stop callbacks have been // invoked does not cause problems. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + StopNotifySession_SessionDeleted2) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StopNotifySession_SessionDeleted2) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -1950,6 +2264,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, characteristic1_, BluetoothRemoteGattDescriptor::ClientCharacteristicConfigurationUuid() .canonical_value()); + base::RunLoop().RunUntilIdle(); ASSERT_EQ(1u, characteristic1_->GetDescriptors().size()); // Start notify sessions. @@ -1998,10 +2313,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #define MAYBE_StopNotifySession_Cancelled DISABLED_StopNotifySession_Cancelled #endif // Tests that cancelling StopNotifySession works. -// TODO(crbug.com/636270): Enable on Windows when SubscribeToNotifications is -// implemented. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + StopNotifySession_Cancelled) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StopNotifySession_Cancelled) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -2030,8 +2348,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, DISABLED_StopNotifySession_AfterDeleted #endif // Tests that deleted sessions are stopped. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + StopNotifySession_AfterDeleted) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StopNotifySession_AfterDeleted) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -2071,8 +2394,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #define MAYBE_StopNotifySession_OnIndicate DISABLED_StopNotifySession_OnIndicate #endif // Tests StopNotifySession success on a characteristic that enabled Indicate. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + StopNotifySession_OnIndicate) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StopNotifySession_OnIndicate) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -2103,8 +2431,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #endif // Tests StopNotifySession success on a characteristic that enabled Notify & // Indicate. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + StopNotifySession_OnNotifyAndIndicate) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StopNotifySession_OnNotifyAndIndicate) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -2133,7 +2466,12 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #define MAYBE_StopNotifySession_Error DISABLED_StopNotifySession_Error #endif // Tests StopNotifySession error +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + StopNotifySession_Error) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StopNotifySession_Error) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -2164,8 +2502,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StopNotifySession_Error) { #define MAYBE_StopNotifySession_Multiple1 DISABLED_StopNotifySession_Multiple1 #endif // Tests multiple StopNotifySession calls for a single session. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + StopNotifySession_Multiple1) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StopNotifySession_Multiple1) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -2177,6 +2520,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, characteristic1_, BluetoothRemoteGattDescriptor::ClientCharacteristicConfigurationUuid() .canonical_value()); + base::RunLoop().RunUntilIdle(); ASSERT_EQ(1u, characteristic1_->GetDescriptors().size()); // Start notify session @@ -2214,8 +2558,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #define MAYBE_StopNotifySession_Multiple2 DISABLED_StopNotifySession_Multiple2 #endif // Tests multiple StartNotifySession calls and multiple StopNotifySession calls. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + StopNotifySession_Multiple2) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StopNotifySession_Multiple2) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -2227,6 +2576,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, characteristic1_, BluetoothRemoteGattDescriptor::ClientCharacteristicConfigurationUuid() .canonical_value()); + base::RunLoop().RunUntilIdle(); ASSERT_EQ(1u, characteristic1_->GetDescriptors().size()); // Start notify sessions @@ -2276,8 +2626,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #endif // Tests starting a new notify session before the previous stop request // resolves. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + StopNotifySession_StopStart) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StopNotifySession_StopStart) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -2288,6 +2643,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, characteristic1_, BluetoothRemoteGattDescriptor::ClientCharacteristicConfigurationUuid() .canonical_value()); + base::RunLoop().RunUntilIdle(); ASSERT_EQ(1u, characteristic1_->GetDescriptors().size()); // Start notify session @@ -2329,8 +2685,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #define MAYBE_StopNotifySession_StartStopStart \ DISABLED_StopNotifySession_StartStopStart #endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + StopNotifySession_StartStopStart) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StopNotifySession_StartStopStart) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -2390,8 +2751,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #endif // Tests starting a new notify session before the previous stop requests // resolve. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + StopNotifySession_StopStopStart) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StopNotifySession_StopStopStart) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -2402,6 +2768,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, characteristic1_, BluetoothRemoteGattDescriptor::ClientCharacteristicConfigurationUuid() .canonical_value()); + base::RunLoop().RunUntilIdle(); ASSERT_EQ(1u, characteristic1_->GetDescriptors().size()); // Start notify session @@ -2458,8 +2825,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #define MAYBE_StopNotifySession_Reentrant_Success_Stop \ DISABLED_StopNotifySession_Reentrant_Success_Stop #endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + StopNotifySession_Reentrant_Success_Stop) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StopNotifySession_Reentrant_Success_Stop) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -2470,6 +2842,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, characteristic1_, BluetoothRemoteGattDescriptor::ClientCharacteristicConfigurationUuid() .canonical_value()); + base::RunLoop().RunUntilIdle(); ASSERT_EQ(1u, characteristic1_->GetDescriptors().size()); // Start notify session @@ -2507,8 +2880,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #define MAYBE_StopNotifySession_Reentrant_Stop_StartSuccess \ DISABLED_StopNotifySession_Reentrant_Stop_StartSuccess #endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + StopNotifySession_Reentrant_Stop_StartSuccess) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StopNotifySession_Reentrant_Stop_StartSuccess) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -2557,8 +2935,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #define MAYBE_StopNotifySession_Reentrant_Stop_StartError \ DISABLED_StopNotifySession_Reentrant_Stop_StartError #endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + StopNotifySession_Reentrant_Stop_StartError) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_StopNotifySession_Reentrant_Stop_StartError) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -2605,7 +2988,11 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #endif // TODO(786473) Android should report that services are discovered when a // characteristic is added, but currently does not. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, GattCharacteristicAdded) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GattCharacteristicAdded) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -2618,15 +3005,20 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GattCharacteristicAdded) { EXPECT_EQ(1, observer.gatt_services_discovered_count()); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_GattCharacteristicValueChanged GattCharacteristicValueChanged #else #define MAYBE_GattCharacteristicValueChanged \ DISABLED_GattCharacteristicValueChanged #endif // Tests Characteristic Value changes during a Notify Session. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + GattCharacteristicValueChanged) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GattCharacteristicValueChanged) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -2686,7 +3078,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, observer.previous_characteristic_value_changed_values()); } -#if defined(OS_ANDROID) || defined(OS_WIN) +#if defined(OS_ANDROID) #define MAYBE_GattCharacteristicValueChanged_AfterDeleted \ GattCharacteristicValueChanged_AfterDeleted #else @@ -2697,8 +3089,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, // destroyed. // macOS: Not applicable: This can never happen if CBPeripheral delegate is set // to nil. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWin32Only, + GattCharacteristicValueChanged_AfterDeleted) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GattCharacteristicValueChanged_AfterDeleted) { +#endif ASSERT_NO_FATAL_FAILURE(StartNotifyBoilerplate( /* properties: NOTIFY */ 0x10, NotifyValueState::NOTIFY)); TestBluetoothAdapterObserver observer(adapter_); @@ -2714,25 +3111,44 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count()); } -#if defined(OS_ANDROID) || defined(OS_WIN) +#if defined(OS_ANDROID) #define MAYBE_GetDescriptors_FindNone GetDescriptors_FindNone #else #define MAYBE_GetDescriptors_FindNone DISABLED_GetDescriptors_FindNone #endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, GetDescriptors_FindNone) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GetDescriptors_FindNone) { +#endif + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate()); EXPECT_EQ(0u, characteristic1_->GetDescriptors().size()); } -#if defined(OS_ANDROID) || defined(OS_WIN) +#if defined(OS_ANDROID) #define MAYBE_GetDescriptors_and_GetDescriptor GetDescriptors_and_GetDescriptor #else #define MAYBE_GetDescriptors_and_GetDescriptor \ DISABLED_GetDescriptors_and_GetDescriptor #endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + GetDescriptors_and_GetDescriptor) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GetDescriptors_and_GetDescriptor) { +#endif + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate()); // Add several Descriptors: @@ -2744,6 +3160,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, SimulateGattDescriptor(characteristic1_, uuid2.canonical_value()); SimulateGattDescriptor(characteristic2_, uuid3.canonical_value()); SimulateGattDescriptor(characteristic2_, uuid4.canonical_value()); + base::RunLoop().RunUntilIdle(); // Verify that GetDescriptor can retrieve descriptors again by ID, // and that the same Descriptor is returned when searched by ID. @@ -2775,12 +3192,21 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, EXPECT_FALSE(c1_uuid1 == uuid3 || c1_uuid2 == uuid3); } -#if defined(OS_ANDROID) || defined(OS_WIN) +#if defined(OS_ANDROID) #define MAYBE_GetDescriptorsByUUID GetDescriptorsByUUID #else #define MAYBE_GetDescriptorsByUUID DISABLED_GetDescriptorsByUUID #endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, GetDescriptorsByUUID) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GetDescriptorsByUUID) { +#endif + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate()); // Add several Descriptors: @@ -2791,6 +3217,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_GetDescriptorsByUUID) { SimulateGattDescriptor(characteristic1_, id2.canonical_value()); SimulateGattDescriptor(characteristic2_, id3.canonical_value()); SimulateGattDescriptor(characteristic2_, id3.canonical_value()); + base::RunLoop().RunUntilIdle(); EXPECT_NE(characteristic2_->GetDescriptorsByUUID(id3).at(0)->GetIdentifier(), characteristic2_->GetDescriptorsByUUID(id3).at(1)->GetIdentifier()); @@ -2872,12 +3299,18 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteDuringDisconnect) { DISABLED_WriteWithoutResponseOnlyCharacteristic_WriteRemoteCharacteristicDuringDisconnect #endif // Tests that writing without response during a disconnect results in an error. -// Only applies to macOS whose events arrive all on the UI thread. See other -// *DuringDisconnect tests for Android and Windows whose events arrive on a -// different thread. +// Only applies to macOS and WinRT whose events arrive all on the UI thread. See +// other *DuringDisconnect tests for Android and Windows whose events arrive on +// a different thread. +#if defined(OS_WIN) +TEST_P( + BluetoothRemoteGattCharacteristicTestWinrtOnly, + WriteWithoutResponseOnlyCharacteristic_WriteRemoteCharacteristicDuringDisconnect) { +#else TEST_F( BluetoothRemoteGattCharacteristicTest, MAYBE_WriteWithoutResponseOnlyCharacteristic_WriteRemoteCharacteristicDuringDisconnect) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -2888,7 +3321,7 @@ TEST_F( characteristic1_->WriteRemoteCharacteristic( std::vector<uint8_t>(), GetCallback(Call::NOT_EXPECTED), GetGattErrorCallback(Call::EXPECTED)); - SimulateGattDisconnection(device_); + SimulateDeviceBreaksConnection(adapter_->GetDevices()[0]); base::RunLoop().RunUntilIdle(); EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED, @@ -2973,8 +3406,13 @@ TEST_F( // Tests that WriteWithoutResponse fails when a characteristic does not have the // required property. // TODO(https://crbug.com/831524): Enable for other platforms once supported. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + WriteWithoutResponse_PropertyNotPresent) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteWithoutResponse_PropertyNotPresent) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -2996,8 +3434,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, // Tests that WriteWithoutResponse fails when a characteristic already has a // pending write. // TODO(https://crbug.com/831524): Enable for other platforms once supported. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + WriteWithoutResponse_PendingWrite) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteWithoutResponse_PendingWrite) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -3034,8 +3477,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, // Tests that WriteWithoutResponse fails when a characteristic already has a // pending read. // TODO(https://crbug.com/831524): Enable for other platforms once supported. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + WriteWithoutResponse_PendingRead) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteWithoutResponse_PendingRead) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -3071,8 +3519,13 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, // Tests that WriteWithoutResponse indicates success if the proper conditions // are met. // TODO(https://crbug.com/831524): Enable for other platforms once supported. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + WriteWithoutResponse_Success) { +#else TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteWithoutResponse_Success) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -3287,4 +3740,21 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, ExtraDidDiscoverDescriptorsCall) { } #endif // defined(OS_MACOSX) +#if defined(OS_WIN) +INSTANTIATE_TEST_CASE_P( + /* no prefix */, + BluetoothRemoteGattCharacteristicTestWinrt, + ::testing::Bool()); + +INSTANTIATE_TEST_CASE_P( + /* no prefix */, + BluetoothRemoteGattCharacteristicTestWin32Only, + ::testing::Values(false)); + +INSTANTIATE_TEST_CASE_P( + /* no prefix */, + BluetoothRemoteGattCharacteristicTestWinrtOnly, + ::testing::Values(true)); +#endif // defined(OS_WIN) + } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_winrt.cc b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_winrt.cc index 93166b8b1fa..b18b7d5c166 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_winrt.cc +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_winrt.cc @@ -4,31 +4,127 @@ #include "device/bluetooth/bluetooth_remote_gatt_characteristic_winrt.h" +#include <utility> + +#include "base/bind_helpers.h" #include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/win/winrt_storage_util.h" +#include "device/bluetooth/bluetooth_adapter.h" +#include "device/bluetooth/bluetooth_device.h" +#include "device/bluetooth/bluetooth_gatt_discoverer_winrt.h" +#include "device/bluetooth/bluetooth_remote_gatt_descriptor_winrt.h" +#include "device/bluetooth/bluetooth_remote_gatt_service.h" +#include "device/bluetooth/bluetooth_remote_gatt_service_winrt.h" #include "device/bluetooth/bluetooth_uuid.h" +#include "device/bluetooth/event_utils_winrt.h" namespace device { -BluetoothRemoteGattCharacteristicWinrt:: - BluetoothRemoteGattCharacteristicWinrt() = default; +namespace { + +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + GattCharacteristicProperties; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + GattClientCharacteristicConfigurationDescriptorValue; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + GattClientCharacteristicConfigurationDescriptorValue_Indicate; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + GattClientCharacteristicConfigurationDescriptorValue_None; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + GattClientCharacteristicConfigurationDescriptorValue_Notify; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + GattCommunicationStatus; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + GattCommunicationStatus_Success; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::GattReadResult; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + GattWriteOption_WriteWithResponse; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + GattWriteOption_WriteWithoutResponse; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + GattWriteResult; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattCharacteristic; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattCharacteristic3; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattReadResult; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattReadResult2; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattValueChangedEventArgs; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattWriteResult; +using ABI::Windows::Foundation::IAsyncOperation; +using ABI::Windows::Storage::Streams::IBuffer; +using Microsoft::WRL::ComPtr; + +} // namespace + +// static +std::unique_ptr<BluetoothRemoteGattCharacteristicWinrt> +BluetoothRemoteGattCharacteristicWinrt::Create( + BluetoothRemoteGattService* service, + ComPtr<IGattCharacteristic> characteristic) { + DCHECK(characteristic); + GUID guid; + HRESULT hr = characteristic->get_Uuid(&guid); + if (FAILED(hr)) { + VLOG(2) << "Getting UUID failed: " << logging::SystemErrorCodeToString(hr); + return nullptr; + } + + GattCharacteristicProperties properties; + hr = characteristic->get_CharacteristicProperties(&properties); + if (FAILED(hr)) { + VLOG(2) << "Getting Properties failed: " + << logging::SystemErrorCodeToString(hr); + return nullptr; + } + + uint16_t attribute_handle; + hr = characteristic->get_AttributeHandle(&attribute_handle); + if (FAILED(hr)) { + VLOG(2) << "Getting AttributeHandle failed: " + << logging::SystemErrorCodeToString(hr); + return nullptr; + } + + return base::WrapUnique(new BluetoothRemoteGattCharacteristicWinrt( + service, std::move(characteristic), BluetoothUUID(guid), properties, + attribute_handle)); +} BluetoothRemoteGattCharacteristicWinrt:: - ~BluetoothRemoteGattCharacteristicWinrt() = default; + ~BluetoothRemoteGattCharacteristicWinrt() { + if (pending_read_callbacks_) { + pending_read_callbacks_->error_callback.Run( + BluetoothGattService::GATT_ERROR_FAILED); + } + + if (pending_write_callbacks_) { + pending_write_callbacks_->error_callback.Run( + BluetoothGattService::GATT_ERROR_FAILED); + } + + if (value_changed_token_) + RemoveValueChangedHandler(); +} std::string BluetoothRemoteGattCharacteristicWinrt::GetIdentifier() const { - NOTIMPLEMENTED(); - return std::string(); + return identifier_; } BluetoothUUID BluetoothRemoteGattCharacteristicWinrt::GetUUID() const { - NOTIMPLEMENTED(); - return BluetoothUUID(); + return uuid_; } BluetoothGattCharacteristic::Properties BluetoothRemoteGattCharacteristicWinrt::GetProperties() const { - NOTIMPLEMENTED(); - return Properties(); + return properties_; } BluetoothGattCharacteristic::Permissions @@ -44,35 +140,494 @@ const std::vector<uint8_t>& BluetoothRemoteGattCharacteristicWinrt::GetValue() BluetoothRemoteGattService* BluetoothRemoteGattCharacteristicWinrt::GetService() const { - NOTIMPLEMENTED(); - return nullptr; + return service_; } void BluetoothRemoteGattCharacteristicWinrt::ReadRemoteCharacteristic( const ValueCallback& callback, const ErrorCallback& error_callback) { - NOTIMPLEMENTED(); + if (!(GetProperties() & PROPERTY_READ)) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothRemoteGattService::GATT_ERROR_NOT_PERMITTED)); + return; + } + + if (pending_read_callbacks_ || pending_write_callbacks_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothRemoteGattService::GATT_ERROR_IN_PROGRESS)); + return; + } + + ComPtr<IAsyncOperation<GattReadResult*>> read_value_op; + HRESULT hr = characteristic_->ReadValueAsync(&read_value_op); + if (FAILED(hr)) { + VLOG(2) << "GattCharacteristic::ReadValueAsync failed: " + << logging::SystemErrorCodeToString(hr); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothRemoteGattService::GATT_ERROR_FAILED)); + return; + } + + hr = PostAsyncResults( + std::move(read_value_op), + base::BindOnce(&BluetoothRemoteGattCharacteristicWinrt::OnReadValue, + weak_ptr_factory_.GetWeakPtr())); + + if (FAILED(hr)) { + VLOG(2) << "PostAsyncResults failed: " + << logging::SystemErrorCodeToString(hr); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothRemoteGattService::GATT_ERROR_FAILED)); + return; + } + + pending_read_callbacks_ = + std::make_unique<PendingReadCallbacks>(callback, error_callback); } void BluetoothRemoteGattCharacteristicWinrt::WriteRemoteCharacteristic( const std::vector<uint8_t>& value, const base::Closure& callback, const ErrorCallback& error_callback) { - NOTIMPLEMENTED(); + if (!(GetProperties() & PROPERTY_WRITE) && + !(GetProperties() & PROPERTY_WRITE_WITHOUT_RESPONSE)) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothRemoteGattService::GATT_ERROR_NOT_PERMITTED)); + return; + } + + if (pending_read_callbacks_ || pending_write_callbacks_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothRemoteGattService::GATT_ERROR_IN_PROGRESS)); + return; + } + + ComPtr<IGattCharacteristic3> characteristic_3; + HRESULT hr = characteristic_.As(&characteristic_3); + if (FAILED(hr)) { + VLOG(2) << "As IGattCharacteristic3 failed: " + << logging::SystemErrorCodeToString(hr); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothRemoteGattService::GATT_ERROR_FAILED)); + return; + } + + ComPtr<IBuffer> buffer; + hr = base::win::CreateIBufferFromData(value.data(), value.size(), &buffer); + if (FAILED(hr)) { + VLOG(2) << "base::win::CreateIBufferFromData failed: " + << logging::SystemErrorCodeToString(hr); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothRemoteGattService::GATT_ERROR_FAILED)); + return; + } + + ComPtr<IAsyncOperation<GattWriteResult*>> write_value_op; + hr = characteristic_3->WriteValueWithResultAndOptionAsync( + buffer.Get(), + (GetProperties() & PROPERTY_WRITE) ? GattWriteOption_WriteWithResponse + : GattWriteOption_WriteWithoutResponse, + + &write_value_op); + if (FAILED(hr)) { + VLOG(2) << "GattCharacteristic::WriteValueWithResultAndOptionAsync failed: " + << logging::SystemErrorCodeToString(hr); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothRemoteGattService::GATT_ERROR_FAILED)); + return; + } + + hr = PostAsyncResults(std::move(write_value_op), + base::BindOnce(&BluetoothRemoteGattCharacteristicWinrt:: + OnWriteValueWithResultAndOption, + weak_ptr_factory_.GetWeakPtr())); + + if (FAILED(hr)) { + VLOG(2) << "PostAsyncResults failed: " + << logging::SystemErrorCodeToString(hr); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothRemoteGattService::GATT_ERROR_FAILED)); + return; + } + + pending_write_callbacks_ = + std::make_unique<PendingWriteCallbacks>(callback, error_callback); +} + +void BluetoothRemoteGattCharacteristicWinrt::UpdateDescriptors( + BluetoothGattDiscovererWinrt* gatt_discoverer) { + const auto* gatt_descriptors = + gatt_discoverer->GetDescriptors(attribute_handle_); + DCHECK(gatt_descriptors); + + // Instead of clearing out |descriptors_| and creating each descriptor + // from scratch, we create a new map and move already existing descriptors + // into it in order to preserve pointer stability. + DescriptorMap descriptors; + for (const auto& gatt_descriptor : *gatt_descriptors) { + auto descriptor = + BluetoothRemoteGattDescriptorWinrt::Create(this, gatt_descriptor.Get()); + if (!descriptor) + continue; + + std::string identifier = descriptor->GetIdentifier(); + auto iter = descriptors_.find(identifier); + if (iter != descriptors_.end()) + descriptors.emplace(std::move(*iter)); + else + descriptors.emplace(std::move(identifier), std::move(descriptor)); + } + + std::swap(descriptors, descriptors_); +} + +bool BluetoothRemoteGattCharacteristicWinrt::WriteWithoutResponse( + base::span<const uint8_t> value) { + if (!(GetProperties() & PROPERTY_WRITE_WITHOUT_RESPONSE)) + return false; + + if (pending_read_callbacks_ || pending_write_callbacks_) + return false; + + ComPtr<IGattCharacteristic3> characteristic_3; + HRESULT hr = characteristic_.As(&characteristic_3); + if (FAILED(hr)) { + VLOG(2) << "As IGattCharacteristic3 failed: " + << logging::SystemErrorCodeToString(hr); + return false; + } + + ComPtr<IBuffer> buffer; + hr = base::win::CreateIBufferFromData(value.data(), value.size(), &buffer); + if (FAILED(hr)) { + VLOG(2) << "base::win::CreateIBufferFromData failed: " + << logging::SystemErrorCodeToString(hr); + return false; + } + + ComPtr<IAsyncOperation<GattWriteResult*>> write_value_op; + // Note: As we are ignoring the result WriteValueWithOptionAsync() would work + // as well, but re-using WriteValueWithResultAndOptionAsync() does simplify + // the testing code and there is no difference in production. + hr = characteristic_3->WriteValueWithResultAndOptionAsync( + buffer.Get(), GattWriteOption_WriteWithoutResponse, &write_value_op); + if (FAILED(hr)) { + VLOG(2) << "GattCharacteristic::WriteValueWithResultAndOptionAsync failed: " + << logging::SystemErrorCodeToString(hr); + return false; + } + + // While we are ignoring the response, we still post the async_op in order to + // extend its lifetime until the operation has completed. + hr = PostAsyncResults(std::move(write_value_op), base::DoNothing()); + if (FAILED(hr)) { + VLOG(2) << "PostAsyncResults failed: " + << logging::SystemErrorCodeToString(hr); + return false; + } + + return true; +} + +IGattCharacteristic* +BluetoothRemoteGattCharacteristicWinrt::GetCharacteristicForTesting() { + return characteristic_.Get(); } void BluetoothRemoteGattCharacteristicWinrt::SubscribeToNotifications( BluetoothRemoteGattDescriptor* ccc_descriptor, const base::Closure& callback, const ErrorCallback& error_callback) { - NOTIMPLEMENTED(); + value_changed_token_ = AddTypedEventHandler( + characteristic_.Get(), &IGattCharacteristic::add_ValueChanged, + base::BindRepeating( + &BluetoothRemoteGattCharacteristicWinrt::OnValueChanged, + weak_ptr_factory_.GetWeakPtr())); + + if (!value_changed_token_) { + VLOG(2) << "Adding Value Changed Handler failed."; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothRemoteGattService::GATT_ERROR_FAILED)); + return; + } + + WriteCccDescriptor( + (GetProperties() & PROPERTY_NOTIFY) + ? GattClientCharacteristicConfigurationDescriptorValue_Notify + : GattClientCharacteristicConfigurationDescriptorValue_Indicate, + callback, error_callback); } void BluetoothRemoteGattCharacteristicWinrt::UnsubscribeFromNotifications( BluetoothRemoteGattDescriptor* ccc_descriptor, const base::Closure& callback, const ErrorCallback& error_callback) { - NOTIMPLEMENTED(); + WriteCccDescriptor( + GattClientCharacteristicConfigurationDescriptorValue_None, + // Wrap the success and error callbacks in a lambda, so that we can notify + // callers whether removing the event handler succeeded after the + // descriptor has been written to. + base::BindOnce( + [](base::WeakPtr<BluetoothRemoteGattCharacteristicWinrt> + characteristic, + base::OnceClosure callback, ErrorCallback error_callback) { + if (characteristic && + !characteristic->RemoveValueChangedHandler()) { + error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED); + return; + } + + std::move(callback).Run(); + }, + weak_ptr_factory_.GetWeakPtr(), callback, error_callback), + error_callback); +} + +BluetoothRemoteGattCharacteristicWinrt::PendingReadCallbacks:: + PendingReadCallbacks(ValueCallback callback, ErrorCallback error_callback) + : callback(std::move(callback)), + error_callback(std::move(error_callback)) {} +BluetoothRemoteGattCharacteristicWinrt::PendingReadCallbacks:: + ~PendingReadCallbacks() = default; + +BluetoothRemoteGattCharacteristicWinrt::PendingWriteCallbacks:: + PendingWriteCallbacks(base::OnceClosure callback, + ErrorCallback error_callback) + : callback(std::move(callback)), + error_callback(std::move(error_callback)) {} +BluetoothRemoteGattCharacteristicWinrt::PendingWriteCallbacks:: + ~PendingWriteCallbacks() = default; + +BluetoothRemoteGattCharacteristicWinrt::BluetoothRemoteGattCharacteristicWinrt( + BluetoothRemoteGattService* service, + ComPtr<IGattCharacteristic> characteristic, + BluetoothUUID uuid, + Properties properties, + uint16_t attribute_handle) + : service_(service), + characteristic_(std::move(characteristic)), + uuid_(std::move(uuid)), + properties_(properties), + attribute_handle_(attribute_handle), + identifier_(base::StringPrintf("%s/%s_%04x", + service_->GetIdentifier().c_str(), + uuid_.value().c_str(), + attribute_handle_)), + weak_ptr_factory_(this) {} + +void BluetoothRemoteGattCharacteristicWinrt::WriteCccDescriptor( + GattClientCharacteristicConfigurationDescriptorValue value, + base::OnceClosure callback, + const ErrorCallback& error_callback) { + DCHECK(!pending_notification_callbacks_); + ComPtr<IGattCharacteristic3> characteristic_3; + HRESULT hr = characteristic_.As(&characteristic_3); + if (FAILED(hr)) { + VLOG(2) << "As IGattCharacteristic3 failed: " + << logging::SystemErrorCodeToString(hr); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothRemoteGattService::GATT_ERROR_FAILED)); + return; + } + + ComPtr<IAsyncOperation<GattWriteResult*>> write_ccc_descriptor_op; + hr = characteristic_3 + ->WriteClientCharacteristicConfigurationDescriptorWithResultAsync( + value, &write_ccc_descriptor_op); + if (FAILED(hr)) { + VLOG(2) << "GattCharacteristic::" + "WriteClientCharacteristicConfigurationDescriptorWithResultAsync" + " failed: " + << logging::SystemErrorCodeToString(hr); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothRemoteGattService::GATT_ERROR_FAILED)); + return; + } + + hr = PostAsyncResults( + std::move(write_ccc_descriptor_op), + base::BindOnce( + &BluetoothRemoteGattCharacteristicWinrt::OnWriteCccDescriptor, + weak_ptr_factory_.GetWeakPtr())); + + if (FAILED(hr)) { + VLOG(2) << "PostAsyncResults failed: " + << logging::SystemErrorCodeToString(hr); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothRemoteGattService::GATT_ERROR_FAILED)); + return; + } + + pending_notification_callbacks_ = + std::make_unique<PendingNotificationCallbacks>(std::move(callback), + error_callback); +} + +void BluetoothRemoteGattCharacteristicWinrt::OnReadValue( + ComPtr<IGattReadResult> read_result) { + DCHECK(pending_read_callbacks_); + auto pending_read_callbacks = std::move(pending_read_callbacks_); + + if (!read_result) { + pending_read_callbacks->error_callback.Run( + BluetoothGattService::GATT_ERROR_FAILED); + return; + } + + GattCommunicationStatus status; + HRESULT hr = read_result->get_Status(&status); + if (FAILED(hr)) { + VLOG(2) << "Getting GATT Communication Status failed: " + << logging::SystemErrorCodeToString(hr); + pending_read_callbacks->error_callback.Run( + BluetoothGattService::GATT_ERROR_FAILED); + return; + } + + if (status != GattCommunicationStatus_Success) { + VLOG(2) << "Unexpected GattCommunicationStatus: " << status; + ComPtr<IGattReadResult2> read_result_2; + hr = read_result.As(&read_result_2); + if (FAILED(hr)) { + VLOG(2) << "As IGattReadResult2 failed: " + << logging::SystemErrorCodeToString(hr); + pending_read_callbacks->error_callback.Run( + BluetoothGattService::GATT_ERROR_FAILED); + return; + } + + pending_read_callbacks->error_callback.Run( + BluetoothRemoteGattServiceWinrt::GetGattErrorCode(read_result_2.Get())); + return; + } + + ComPtr<IBuffer> value; + hr = read_result->get_Value(&value); + if (FAILED(hr)) { + VLOG(2) << "Getting Characteristic Value failed: " + << logging::SystemErrorCodeToString(hr); + pending_read_callbacks->error_callback.Run( + BluetoothGattService::GATT_ERROR_FAILED); + return; + } + + uint8_t* data = nullptr; + uint32_t length = 0; + hr = base::win::GetPointerToBufferData(value.Get(), &data, &length); + if (FAILED(hr)) { + VLOG(2) << "Getting Pointer To Buffer Data failed: " + << logging::SystemErrorCodeToString(hr); + pending_read_callbacks->error_callback.Run( + BluetoothGattService::GATT_ERROR_FAILED); + return; + } + + value_.assign(data, data + length); + pending_read_callbacks->callback.Run(value_); +} + +void BluetoothRemoteGattCharacteristicWinrt::OnWriteValueWithResultAndOption( + ComPtr<IGattWriteResult> write_result) { + DCHECK(pending_write_callbacks_); + OnWriteImpl(std::move(write_result), std::move(pending_write_callbacks_)); +} + +void BluetoothRemoteGattCharacteristicWinrt::OnWriteCccDescriptor( + ComPtr<IGattWriteResult> write_result) { + OnWriteImpl(std::move(write_result), + std::move(pending_notification_callbacks_)); +} + +void BluetoothRemoteGattCharacteristicWinrt::OnWriteImpl( + ComPtr<IGattWriteResult> write_result, + std::unique_ptr<PendingWriteCallbacks> callbacks) { + if (!write_result) { + callbacks->error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED); + return; + } + + GattCommunicationStatus status; + HRESULT hr = write_result->get_Status(&status); + if (FAILED(hr)) { + VLOG(2) << "Getting GATT Communication Status failed: " + << logging::SystemErrorCodeToString(hr); + callbacks->error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED); + return; + } + + if (status != GattCommunicationStatus_Success) { + VLOG(2) << "Unexpected GattCommunicationStatus: " << status; + callbacks->error_callback.Run( + BluetoothRemoteGattServiceWinrt::GetGattErrorCode(write_result.Get())); + return; + } + + std::move(callbacks->callback).Run(); +} + +void BluetoothRemoteGattCharacteristicWinrt::OnValueChanged( + IGattCharacteristic* characteristic, + IGattValueChangedEventArgs* event_args) { + ComPtr<IBuffer> value; + HRESULT hr = event_args->get_CharacteristicValue(&value); + if (FAILED(hr)) { + VLOG(2) << "Getting Characteristic Value failed: " + << logging::SystemErrorCodeToString(hr); + return; + } + + uint8_t* data = nullptr; + uint32_t length = 0; + hr = base::win::GetPointerToBufferData(value.Get(), &data, &length); + if (FAILED(hr)) { + VLOG(2) << "Getting Pointer To Buffer Data failed: " + << logging::SystemErrorCodeToString(hr); + return; + } + + value_.assign(data, data + length); + service_->GetDevice()->GetAdapter()->NotifyGattCharacteristicValueChanged( + this, value_); +} + +bool BluetoothRemoteGattCharacteristicWinrt::RemoveValueChangedHandler() { + DCHECK(value_changed_token_); + HRESULT hr = characteristic_->remove_ValueChanged(*value_changed_token_); + if (FAILED(hr)) { + VLOG(2) << "Removing the Value Changed Handler failed: " + << logging::SystemErrorCodeToString(hr); + } + + value_changed_token_.reset(); + return SUCCEEDED(hr); } } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_winrt.h b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_winrt.h index ca1b3c0255e..30e242a9a81 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_winrt.h +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_winrt.h @@ -5,8 +5,12 @@ #ifndef DEVICE_BLUETOOTH_BLUETOOTH_REMOTE_GATT_CHARACTERISTIC_WINRT_H_ #define DEVICE_BLUETOOTH_BLUETOOTH_REMOTE_GATT_CHARACTERISTIC_WINRT_H_ +#include <windows.devices.bluetooth.genericattributeprofile.h> +#include <wrl/client.h> + #include <stdint.h> +#include <memory> #include <string> #include <vector> @@ -14,17 +18,22 @@ #include "base/macros.h" #include "device/bluetooth/bluetooth_export.h" #include "device/bluetooth/bluetooth_remote_gatt_characteristic.h" +#include "device/bluetooth/bluetooth_uuid.h" namespace device { class BluetoothRemoteGattDescriptor; +class BluetoothGattDiscovererWinrt; class BluetoothRemoteGattService; -class BluetoothUUID; class DEVICE_BLUETOOTH_EXPORT BluetoothRemoteGattCharacteristicWinrt : public BluetoothRemoteGattCharacteristic { public: - BluetoothRemoteGattCharacteristicWinrt(); + static std::unique_ptr<BluetoothRemoteGattCharacteristicWinrt> Create( + BluetoothRemoteGattService* service, + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth:: + GenericAttributeProfile::IGattCharacteristic> + characteristic); ~BluetoothRemoteGattCharacteristicWinrt() override; // BluetoothGattCharacteristic: @@ -41,6 +50,13 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothRemoteGattCharacteristicWinrt void WriteRemoteCharacteristic(const std::vector<uint8_t>& value, const base::Closure& callback, const ErrorCallback& error_callback) override; + bool WriteWithoutResponse(base::span<const uint8_t> value) override; + + void UpdateDescriptors(BluetoothGattDiscovererWinrt* gatt_discoverer); + + ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattCharacteristic* + GetCharacteristicForTesting(); protected: // BluetoothRemoteGattCharacteristic: @@ -53,7 +69,84 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothRemoteGattCharacteristicWinrt const ErrorCallback& error_callback) override; private: + struct PendingReadCallbacks { + PendingReadCallbacks(ValueCallback callback, ErrorCallback error_callback); + ~PendingReadCallbacks(); + + ValueCallback callback; + ErrorCallback error_callback; + }; + + struct PendingWriteCallbacks { + PendingWriteCallbacks(base::OnceClosure callback, + ErrorCallback error_callback); + ~PendingWriteCallbacks(); + + base::OnceClosure callback; + ErrorCallback error_callback; + }; + + using PendingNotificationCallbacks = PendingWriteCallbacks; + + BluetoothRemoteGattCharacteristicWinrt( + BluetoothRemoteGattService* service, + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth:: + GenericAttributeProfile::IGattCharacteristic> + characteristic, + BluetoothUUID uuid, + Properties proporties, + uint16_t attribute_handle); + + void WriteCccDescriptor( + ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + GattClientCharacteristicConfigurationDescriptorValue value, + base::OnceClosure callback, + const ErrorCallback& error_callback); + + void OnReadValue(Microsoft::WRL::ComPtr< + ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattReadResult> read_result); + + void OnWriteValueWithResultAndOption( + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth:: + GenericAttributeProfile::IGattWriteResult> + write_result); + + void OnWriteCccDescriptor( + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth:: + GenericAttributeProfile::IGattWriteResult> + write_result); + + void OnWriteImpl( + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth:: + GenericAttributeProfile::IGattWriteResult> + write_result, + std::unique_ptr<PendingWriteCallbacks> callbacks); + + void OnValueChanged( + ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattCharacteristic* characteristic, + ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattValueChangedEventArgs* event_args); + + bool RemoveValueChangedHandler(); + + BluetoothRemoteGattService* service_; + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth:: + GenericAttributeProfile::IGattCharacteristic> + characteristic_; + BluetoothUUID uuid_; + Properties properties_; + uint16_t attribute_handle_; + std::string identifier_; std::vector<uint8_t> value_; + std::unique_ptr<PendingReadCallbacks> pending_read_callbacks_; + std::unique_ptr<PendingWriteCallbacks> pending_write_callbacks_; + std::unique_ptr<PendingNotificationCallbacks> pending_notification_callbacks_; + base::Optional<EventRegistrationToken> value_changed_token_; + + base::WeakPtrFactory<BluetoothRemoteGattCharacteristicWinrt> + weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(BluetoothRemoteGattCharacteristicWinrt); }; diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_descriptor_unittest.cc b/chromium/device/bluetooth/bluetooth_remote_gatt_descriptor_unittest.cc index 9ef4cfcfe1c..3c3e5d0726d 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_descriptor_unittest.cc +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_descriptor_unittest.cc @@ -22,7 +22,12 @@ namespace device { -class BluetoothRemoteGattDescriptorTest : public BluetoothTest { +class BluetoothRemoteGattDescriptorTest : +#if defined(OS_WIN) + public BluetoothTestWinrt { +#else + public BluetoothTest { +#endif public: // Creates adapter_, device_, service_, characteristic_, // descriptor1_, & descriptor2_. @@ -40,12 +45,14 @@ class BluetoothRemoteGattDescriptorTest : public BluetoothTest { ASSERT_EQ(1u, device_->GetGattServices().size()); service_ = device_->GetGattServices()[0]; SimulateGattCharacteristic(service_, kTestUUIDDeviceName, 0); + base::RunLoop().RunUntilIdle(); ASSERT_EQ(1u, service_->GetCharacteristics().size()); characteristic_ = service_->GetCharacteristics()[0]; SimulateGattDescriptor(characteristic_, kTestUUIDCharacteristicUserDescription); SimulateGattDescriptor(characteristic_, kTestUUIDClientCharacteristicConfiguration); + base::RunLoop().RunUntilIdle(); ASSERT_EQ(2u, characteristic_->GetDescriptors().size()); descriptor1_ = characteristic_->GetDescriptors()[0]; descriptor2_ = characteristic_->GetDescriptors()[1]; @@ -59,12 +66,21 @@ class BluetoothRemoteGattDescriptorTest : public BluetoothTest { BluetoothRemoteGattDescriptor* descriptor2_ = nullptr; }; +#if defined(OS_WIN) +using BluetoothRemoteGattDescriptorTestWinrtOnly = + BluetoothRemoteGattDescriptorTest; +#endif + #if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_GetIdentifier GetIdentifier #else #define MAYBE_GetIdentifier DISABLED_GetIdentifier #endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattDescriptorTestWinrtOnly, GetIdentifier) { +#else TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_GetIdentifier) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -102,6 +118,7 @@ TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_GetIdentifier) { SimulateGattCharacteristic(service2, kTestUUIDDeviceName, /* properties */ 0); SimulateGattCharacteristic(service3, kTestUUIDDeviceName, /* properties */ 0); SimulateGattCharacteristic(service3, kTestUUIDDeviceName, /* properties */ 0); + base::RunLoop().RunUntilIdle(); BluetoothRemoteGattCharacteristic* char1 = service1->GetCharacteristics()[0]; BluetoothRemoteGattCharacteristic* char2 = service1->GetCharacteristics()[1]; BluetoothRemoteGattCharacteristic* char3 = service2->GetCharacteristics()[0]; @@ -117,6 +134,7 @@ TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_GetIdentifier) { SimulateGattDescriptor(char4, kTestUUIDCharacteristicUserDescription); SimulateGattDescriptor(char5, kTestUUIDCharacteristicUserDescription); SimulateGattDescriptor(char6, kTestUUIDCharacteristicUserDescription); + base::RunLoop().RunUntilIdle(); BluetoothRemoteGattDescriptor* desc1 = char1->GetDescriptors()[0]; BluetoothRemoteGattDescriptor* desc2 = char2->GetDescriptors()[0]; BluetoothRemoteGattDescriptor* desc3 = char3->GetDescriptors()[0]; @@ -151,7 +169,11 @@ TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_GetIdentifier) { #else #define MAYBE_GetUUID DISABLED_GetUUID #endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattDescriptorTestWinrtOnly, GetUUID) { +#else TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_GetUUID) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -170,6 +192,7 @@ TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_GetUUID) { SimulateGattCharacteristic(service, kTestUUIDDeviceName, /* properties */ 0); + base::RunLoop().RunUntilIdle(); ASSERT_EQ(1u, service->GetCharacteristics().size()); BluetoothRemoteGattCharacteristic* characteristic = service->GetCharacteristics()[0]; @@ -181,6 +204,7 @@ TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_GetUUID) { kTestUUIDCharacteristicUserDescription); SimulateGattDescriptor(characteristic, kTestUUIDClientCharacteristicConfiguration); + base::RunLoop().RunUntilIdle(); ASSERT_EQ(2u, characteristic->GetDescriptors().size()); BluetoothRemoteGattDescriptor* descriptor1 = characteristic->GetDescriptors()[0]; @@ -201,7 +225,11 @@ TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_GetUUID) { #define MAYBE_ReadRemoteDescriptor_Empty DISABLED_ReadRemoteDescriptor_Empty #endif // Tests ReadRemoteDescriptor and GetValue with empty value buffer. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattDescriptorTestWinrtOnly, ReadRemoteDescriptor_Empty) { +#else TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_ReadRemoteDescriptor_Empty) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -229,7 +257,12 @@ TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_ReadRemoteDescriptor_Empty) { #define MAYBE_WriteRemoteDescriptor_Empty DISABLED_WriteRemoteDescriptor_Empty #endif // Tests WriteRemoteDescriptor with empty value buffer. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattDescriptorTestWinrtOnly, + WriteRemoteDescriptor_Empty) { +#else TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_WriteRemoteDescriptor_Empty) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -318,7 +351,11 @@ TEST_F(BluetoothRemoteGattDescriptorTest, #define MAYBE_ReadRemoteDescriptor DISABLED_ReadRemoteDescriptor #endif // Tests ReadRemoteDescriptor and GetValue with non-empty value buffer. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattDescriptorTestWinrtOnly, ReadRemoteDescriptor) { +#else TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_ReadRemoteDescriptor) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -349,7 +386,11 @@ TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_ReadRemoteDescriptor) { #define MAYBE_WriteRemoteDescriptor DISABLED_WriteRemoteDescriptor #endif // Tests WriteRemoteDescriptor with non-empty value buffer. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattDescriptorTestWinrtOnly, WriteRemoteDescriptor) { +#else TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_WriteRemoteDescriptor) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -374,7 +415,11 @@ TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_WriteRemoteDescriptor) { #define MAYBE_ReadRemoteDescriptor_Twice DISABLED_ReadRemoteDescriptor_Twice #endif // Tests ReadRemoteDescriptor and GetValue multiple times. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattDescriptorTestWinrtOnly, ReadRemoteDescriptor_Twice) { +#else TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_ReadRemoteDescriptor_Twice) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -414,7 +459,12 @@ TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_ReadRemoteDescriptor_Twice) { #define MAYBE_WriteRemoteDescriptor_Twice DISABLED_WriteRemoteDescriptor_Twice #endif // Tests WriteRemoteDescriptor multiple times. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattDescriptorTestWinrtOnly, + WriteRemoteDescriptor_Twice) { +#else TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_WriteRemoteDescriptor_Twice) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -454,8 +504,13 @@ TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_WriteRemoteDescriptor_Twice) { DISABLED_ReadRemoteDescriptor_MultipleDescriptors #endif // Tests ReadRemoteDescriptor on two descriptors. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattDescriptorTestWinrtOnly, + ReadRemoteDescriptor_MultipleDescriptors) { +#else TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_ReadRemoteDescriptor_MultipleDescriptors) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -496,8 +551,13 @@ TEST_F(BluetoothRemoteGattDescriptorTest, DISABLED_WriteRemoteDescriptor_MultipleDescriptors #endif // Tests WriteRemoteDescriptor on two descriptors. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattDescriptorTestWinrtOnly, + WriteRemoteDescriptor_MultipleDescriptors) { +#else TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_WriteRemoteDescriptor_MultipleDescriptors) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -534,7 +594,11 @@ TEST_F(BluetoothRemoteGattDescriptorTest, #define MAYBE_ReadError DISABLED_ReadError #endif // Tests ReadRemoteDescriptor asynchronous error. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattDescriptorTestWinrtOnly, ReadError) { +#else TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_ReadError) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -558,7 +622,11 @@ TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_ReadError) { #define MAYBE_WriteError DISABLED_WriteError #endif // Tests WriteRemoteDescriptor asynchronous error. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattDescriptorTestWinrtOnly, WriteError) { +#else TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_WriteError) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -661,8 +729,13 @@ TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_WriteSynchronousError) { DISABLED_ReadRemoteDescriptor_ReadPending #endif // Tests ReadRemoteDescriptor error with a pending read operation. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattDescriptorTestWinrtOnly, + ReadRemoteDescriptor_ReadPending) { +#else TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_ReadRemoteDescriptor_ReadPending) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -698,8 +771,13 @@ TEST_F(BluetoothRemoteGattDescriptorTest, DISABLED_WriteRemoteDescriptor_WritePending #endif // Tests WriteRemoteDescriptor error with a pending write operation. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattDescriptorTestWinrtOnly, + WriteRemoteDescriptor_WritePending) { +#else TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_WriteRemoteDescriptor_WritePending) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -736,8 +814,13 @@ TEST_F(BluetoothRemoteGattDescriptorTest, DISABLED_ReadRemoteDescriptor_WritePending #endif // Tests ReadRemoteDescriptor error with a pending write operation. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattDescriptorTestWinrtOnly, + ReadRemoteDescriptor_WritePending) { +#else TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_ReadRemoteDescriptor_WritePending) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -773,8 +856,13 @@ TEST_F(BluetoothRemoteGattDescriptorTest, DISABLED_WriteRemoteDescriptor_ReadPending #endif // Tests WriteRemoteDescriptor error with a pending Read operation. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattDescriptorTestWinrtOnly, + WriteRemoteDescriptor_ReadPending) { +#else TEST_F(BluetoothRemoteGattDescriptorTest, MAYBE_WriteRemoteDescriptor_ReadPending) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -904,4 +992,11 @@ TEST_F(BluetoothRemoteGattDescriptorTest, ReadRemoteDescriptor_NSNumber) { } #endif // defined(OS_MACOSX) +#if defined(OS_WIN) +INSTANTIATE_TEST_CASE_P( + /* no prefix */, + BluetoothRemoteGattDescriptorTestWinrtOnly, + ::testing::Values(true)); +#endif // defined(OS_WIN) + } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_descriptor_winrt.cc b/chromium/device/bluetooth/bluetooth_remote_gatt_descriptor_winrt.cc new file mode 100644 index 00000000000..3c7b806fb77 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_descriptor_winrt.cc @@ -0,0 +1,339 @@ +// Copyright 2018 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 "device/bluetooth/bluetooth_remote_gatt_descriptor_winrt.h" + +#include <utility> + +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/win/winrt_storage_util.h" +#include "device/bluetooth/bluetooth_remote_gatt_service_winrt.h" +#include "device/bluetooth/event_utils_winrt.h" + +namespace device { + +namespace { + +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + GattCommunicationStatus; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + GattCommunicationStatus_Success; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::GattReadResult; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + GattWriteResult; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattDescriptor2; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattDescriptor; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattReadResult2; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattReadResult; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattWriteResult; +using ABI::Windows::Foundation::IAsyncOperation; +using ABI::Windows::Storage::Streams::IBuffer; +using Microsoft::WRL::ComPtr; + +} // namespace + +// static +std::unique_ptr<BluetoothRemoteGattDescriptorWinrt> +BluetoothRemoteGattDescriptorWinrt::Create( + BluetoothRemoteGattCharacteristic* characteristic, + ComPtr<IGattDescriptor> descriptor) { + DCHECK(descriptor); + GUID guid; + HRESULT hr = descriptor->get_Uuid(&guid); + if (FAILED(hr)) { + VLOG(2) << "Getting UUID failed: " << logging::SystemErrorCodeToString(hr); + return nullptr; + } + + uint16_t attribute_handle; + hr = descriptor->get_AttributeHandle(&attribute_handle); + if (FAILED(hr)) { + VLOG(2) << "Getting AttributeHandle failed: " + << logging::SystemErrorCodeToString(hr); + return nullptr; + } + + return base::WrapUnique(new BluetoothRemoteGattDescriptorWinrt( + characteristic, std::move(descriptor), BluetoothUUID(guid), + attribute_handle)); +} + +BluetoothRemoteGattDescriptorWinrt::~BluetoothRemoteGattDescriptorWinrt() = + default; + +std::string BluetoothRemoteGattDescriptorWinrt::GetIdentifier() const { + return identifier_; +} + +BluetoothUUID BluetoothRemoteGattDescriptorWinrt::GetUUID() const { + return uuid_; +} + +BluetoothGattCharacteristic::Permissions +BluetoothRemoteGattDescriptorWinrt::GetPermissions() const { + NOTIMPLEMENTED(); + return BluetoothGattCharacteristic::Permissions(); +} + +const std::vector<uint8_t>& BluetoothRemoteGattDescriptorWinrt::GetValue() + const { + return value_; +} + +BluetoothRemoteGattCharacteristic* +BluetoothRemoteGattDescriptorWinrt::GetCharacteristic() const { + return characteristic_; +} + +void BluetoothRemoteGattDescriptorWinrt::ReadRemoteDescriptor( + const ValueCallback& callback, + const ErrorCallback& error_callback) { + if (pending_read_callbacks_ || pending_write_callbacks_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothRemoteGattService::GATT_ERROR_IN_PROGRESS)); + return; + } + + ComPtr<IAsyncOperation<GattReadResult*>> read_value_op; + HRESULT hr = descriptor_->ReadValueAsync(&read_value_op); + if (FAILED(hr)) { + VLOG(2) << "GattDescriptor::ReadValueAsync failed: " + << logging::SystemErrorCodeToString(hr); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothRemoteGattService::GATT_ERROR_FAILED)); + return; + } + + hr = PostAsyncResults( + std::move(read_value_op), + base::BindOnce(&BluetoothRemoteGattDescriptorWinrt::OnReadValue, + weak_ptr_factory_.GetWeakPtr())); + + if (FAILED(hr)) { + VLOG(2) << "PostAsyncResults failed: " + << logging::SystemErrorCodeToString(hr); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothRemoteGattService::GATT_ERROR_FAILED)); + return; + } + + pending_read_callbacks_ = + std::make_unique<PendingReadCallbacks>(callback, error_callback); +} + +void BluetoothRemoteGattDescriptorWinrt::WriteRemoteDescriptor( + const std::vector<uint8_t>& value, + const base::Closure& callback, + const ErrorCallback& error_callback) { + if (pending_read_callbacks_ || pending_write_callbacks_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothRemoteGattService::GATT_ERROR_IN_PROGRESS)); + return; + } + + ComPtr<IGattDescriptor2> descriptor_2; + HRESULT hr = descriptor_.As(&descriptor_2); + if (FAILED(hr)) { + VLOG(2) << "As IGattDescriptor2 failed: " + << logging::SystemErrorCodeToString(hr); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothRemoteGattService::GATT_ERROR_FAILED)); + return; + } + + ComPtr<IBuffer> buffer; + hr = base::win::CreateIBufferFromData(value.data(), value.size(), &buffer); + if (FAILED(hr)) { + VLOG(2) << "base::win::CreateIBufferFromData failed: " + << logging::SystemErrorCodeToString(hr); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothRemoteGattService::GATT_ERROR_FAILED)); + return; + } + + ComPtr<IAsyncOperation<GattWriteResult*>> write_value_op; + hr = descriptor_2->WriteValueWithResultAsync(buffer.Get(), &write_value_op); + if (FAILED(hr)) { + VLOG(2) << "GattDescriptor::WriteValueWithResultAsync failed: " + << logging::SystemErrorCodeToString(hr); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothRemoteGattService::GATT_ERROR_FAILED)); + return; + } + + hr = PostAsyncResults( + std::move(write_value_op), + base::BindOnce( + &BluetoothRemoteGattDescriptorWinrt::OnWriteValueWithResult, + weak_ptr_factory_.GetWeakPtr())); + + if (FAILED(hr)) { + VLOG(2) << "PostAsyncResults failed: " + << logging::SystemErrorCodeToString(hr); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothRemoteGattService::GATT_ERROR_FAILED)); + return; + } + + pending_write_callbacks_ = + std::make_unique<PendingWriteCallbacks>(callback, error_callback); +} + +IGattDescriptor* BluetoothRemoteGattDescriptorWinrt::GetDescriptorForTesting() { + return descriptor_.Get(); +} + +BluetoothRemoteGattDescriptorWinrt::PendingReadCallbacks::PendingReadCallbacks( + ValueCallback callback, + ErrorCallback error_callback) + : callback(std::move(callback)), + error_callback(std::move(error_callback)) {} + +BluetoothRemoteGattDescriptorWinrt::PendingReadCallbacks:: + ~PendingReadCallbacks() = default; + +BluetoothRemoteGattDescriptorWinrt::PendingWriteCallbacks:: + PendingWriteCallbacks(base::OnceClosure callback, + ErrorCallback error_callback) + : callback(std::move(callback)), + error_callback(std::move(error_callback)) {} + +BluetoothRemoteGattDescriptorWinrt::PendingWriteCallbacks:: + ~PendingWriteCallbacks() = default; + +BluetoothRemoteGattDescriptorWinrt::BluetoothRemoteGattDescriptorWinrt( + BluetoothRemoteGattCharacteristic* characteristic, + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth:: + GenericAttributeProfile::IGattDescriptor> + descriptor, + BluetoothUUID uuid, + uint16_t attribute_handle) + : characteristic_(characteristic), + descriptor_(std::move(descriptor)), + uuid_(std::move(uuid)), + identifier_(base::StringPrintf("%s/%s_%04x", + characteristic_->GetIdentifier().c_str(), + uuid_.value().c_str(), + attribute_handle)), + weak_ptr_factory_(this) {} + +void BluetoothRemoteGattDescriptorWinrt::OnReadValue( + ComPtr<IGattReadResult> read_result) { + DCHECK(pending_read_callbacks_); + auto pending_read_callbacks = std::move(pending_read_callbacks_); + + if (!read_result) { + pending_read_callbacks->error_callback.Run( + BluetoothGattService::GATT_ERROR_FAILED); + return; + } + + GattCommunicationStatus status; + HRESULT hr = read_result->get_Status(&status); + if (FAILED(hr)) { + VLOG(2) << "Getting GATT Communication Status failed: " + << logging::SystemErrorCodeToString(hr); + pending_read_callbacks->error_callback.Run( + BluetoothGattService::GATT_ERROR_FAILED); + return; + } + + if (status != GattCommunicationStatus_Success) { + VLOG(2) << "Unexpected GattCommunicationStatus: " << status; + ComPtr<IGattReadResult2> read_result_2; + hr = read_result.As(&read_result_2); + if (FAILED(hr)) { + VLOG(2) << "As IGattReadResult2 failed: " + << logging::SystemErrorCodeToString(hr); + pending_read_callbacks->error_callback.Run( + BluetoothGattService::GATT_ERROR_FAILED); + return; + } + + pending_read_callbacks->error_callback.Run( + BluetoothRemoteGattServiceWinrt::GetGattErrorCode(read_result_2.Get())); + return; + } + + ComPtr<IBuffer> value; + hr = read_result->get_Value(&value); + if (FAILED(hr)) { + VLOG(2) << "Getting Descriptor Value failed: " + << logging::SystemErrorCodeToString(hr); + pending_read_callbacks->error_callback.Run( + BluetoothGattService::GATT_ERROR_FAILED); + return; + } + + uint8_t* data = nullptr; + uint32_t length = 0; + hr = base::win::GetPointerToBufferData(value.Get(), &data, &length); + if (FAILED(hr)) { + VLOG(2) << "Getting Pointer To Buffer Data failed: " + << logging::SystemErrorCodeToString(hr); + pending_read_callbacks->error_callback.Run( + BluetoothGattService::GATT_ERROR_FAILED); + return; + } + + value_.assign(data, data + length); + pending_read_callbacks->callback.Run(value_); +} + +void BluetoothRemoteGattDescriptorWinrt::OnWriteValueWithResult( + ComPtr<IGattWriteResult> write_result) { + DCHECK(pending_write_callbacks_); + auto pending_write_callbacks = std::move(pending_write_callbacks_); + + if (!write_result) { + pending_write_callbacks->error_callback.Run( + BluetoothGattService::GATT_ERROR_FAILED); + return; + } + + GattCommunicationStatus status; + HRESULT hr = write_result->get_Status(&status); + if (FAILED(hr)) { + VLOG(2) << "Getting GATT Communication Status failed: " + << logging::SystemErrorCodeToString(hr); + pending_write_callbacks->error_callback.Run( + BluetoothGattService::GATT_ERROR_FAILED); + return; + } + + if (status != GattCommunicationStatus_Success) { + VLOG(2) << "Unexpected GattCommunicationStatus: " << status; + pending_write_callbacks->error_callback.Run( + BluetoothRemoteGattServiceWinrt::GetGattErrorCode(write_result.Get())); + return; + } + + std::move(pending_write_callbacks->callback).Run(); +} + +} // namespace device diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_descriptor_winrt.h b/chromium/device/bluetooth/bluetooth_remote_gatt_descriptor_winrt.h new file mode 100644 index 00000000000..1f3324b0935 --- /dev/null +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_descriptor_winrt.h @@ -0,0 +1,107 @@ +// Copyright 2018 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. + +#ifndef DEVICE_BLUETOOTH_BLUETOOTH_REMOTE_GATT_DESCRIPTOR_WINRT_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_REMOTE_GATT_DESCRIPTOR_WINRT_H_ + +#include <windows.devices.bluetooth.genericattributeprofile.h> +#include <wrl/client.h> + +#include <stdint.h> + +#include <memory> +#include <string> +#include <vector> + +#include "base/callback_forward.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "device/bluetooth/bluetooth_export.h" +#include "device/bluetooth/bluetooth_remote_gatt_characteristic.h" +#include "device/bluetooth/bluetooth_remote_gatt_descriptor.h" +#include "device/bluetooth/bluetooth_uuid.h" + +namespace device { + +class DEVICE_BLUETOOTH_EXPORT BluetoothRemoteGattDescriptorWinrt + : public BluetoothRemoteGattDescriptor { + public: + static std::unique_ptr<BluetoothRemoteGattDescriptorWinrt> Create( + BluetoothRemoteGattCharacteristic* characteristic, + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth:: + GenericAttributeProfile::IGattDescriptor> + descriptor); + ~BluetoothRemoteGattDescriptorWinrt() override; + + // BluetoothGattDescriptor: + std::string GetIdentifier() const override; + BluetoothUUID GetUUID() const override; + BluetoothGattCharacteristic::Permissions GetPermissions() const override; + + // BluetoothRemoteGattDescriptor: + const std::vector<uint8_t>& GetValue() const override; + BluetoothRemoteGattCharacteristic* GetCharacteristic() const override; + void ReadRemoteDescriptor(const ValueCallback& callback, + const ErrorCallback& error_callback) override; + void WriteRemoteDescriptor(const std::vector<uint8_t>& value, + const base::Closure& callback, + const ErrorCallback& error_callback) override; + + ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattDescriptor* + GetDescriptorForTesting(); + + private: + struct PendingReadCallbacks { + PendingReadCallbacks(ValueCallback callback, ErrorCallback error_callback); + ~PendingReadCallbacks(); + + ValueCallback callback; + ErrorCallback error_callback; + }; + + struct PendingWriteCallbacks { + PendingWriteCallbacks(base::OnceClosure callback, + ErrorCallback error_callback); + ~PendingWriteCallbacks(); + + base::OnceClosure callback; + ErrorCallback error_callback; + }; + + BluetoothRemoteGattDescriptorWinrt( + BluetoothRemoteGattCharacteristic* characteristic, + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth:: + GenericAttributeProfile::IGattDescriptor> + descriptor, + BluetoothUUID uuid, + uint16_t attribute_handle); + + void OnReadValue(Microsoft::WRL::ComPtr< + ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattReadResult> read_result); + + void OnWriteValueWithResult( + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth:: + GenericAttributeProfile::IGattWriteResult> + write_result); + + // Weak. This object is owned by |characteristic_|. + BluetoothRemoteGattCharacteristic* characteristic_; + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth:: + GenericAttributeProfile::IGattDescriptor> + descriptor_; + BluetoothUUID uuid_; + std::string identifier_; + std::vector<uint8_t> value_; + std::unique_ptr<PendingReadCallbacks> pending_read_callbacks_; + std::unique_ptr<PendingWriteCallbacks> pending_write_callbacks_; + + base::WeakPtrFactory<BluetoothRemoteGattDescriptorWinrt> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(BluetoothRemoteGattDescriptorWinrt); +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_REMOTE_GATT_DESCRIPTOR_WINRT_H_ diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_service_unittest.cc b/chromium/device/bluetooth/bluetooth_remote_gatt_service_unittest.cc index 02e770f0f9d..9574c4dd0cc 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_service_unittest.cc +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_service_unittest.cc @@ -24,14 +24,21 @@ namespace device { class BluetoothRemoteGattServiceTest : public BluetoothTest {}; +#if defined(OS_WIN) +class BluetoothRemoteGattServiceTestWinrt : public BluetoothTestWinrt {}; +#endif // Android is excluded because it fires a single discovery event per device. -#if defined(OS_WIN) || defined(OS_MACOSX) +#if defined(OS_MACOSX) #define MAYBE_IsDiscoveryComplete IsDiscoveryComplete #else #define MAYBE_IsDiscoveryComplete DISABLED_IsDiscoveryComplete #endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattServiceTestWinrt, IsDiscoveryComplete) { +#else TEST_F(BluetoothRemoteGattServiceTest, MAYBE_IsDiscoveryComplete) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -51,12 +58,16 @@ TEST_F(BluetoothRemoteGattServiceTest, MAYBE_IsDiscoveryComplete) { EXPECT_TRUE(service->IsDiscoveryComplete()); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_GetIdentifier GetIdentifier #else #define MAYBE_GetIdentifier DISABLED_GetIdentifier #endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattServiceTestWinrt, GetIdentifier) { +#else TEST_F(BluetoothRemoteGattServiceTest, MAYBE_GetIdentifier) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -98,12 +109,16 @@ TEST_F(BluetoothRemoteGattServiceTest, MAYBE_GetIdentifier) { EXPECT_NE(service3->GetIdentifier(), service4->GetIdentifier()); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_GetUUID GetUUID #else #define MAYBE_GetUUID DISABLED_GetUUID #endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattServiceTestWinrt, GetUUID) { +#else TEST_F(BluetoothRemoteGattServiceTest, MAYBE_GetUUID) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -129,12 +144,16 @@ TEST_F(BluetoothRemoteGattServiceTest, MAYBE_GetUUID) { EXPECT_EQ(uuid, device->GetGattServices()[1]->GetUUID()); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_GetCharacteristics_FindNone GetCharacteristics_FindNone #else #define MAYBE_GetCharacteristics_FindNone DISABLED_GetCharacteristics_FindNone #endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattServiceTestWinrt, GetCharacteristics_FindNone) { +#else TEST_F(BluetoothRemoteGattServiceTest, MAYBE_GetCharacteristics_FindNone) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -156,15 +175,20 @@ TEST_F(BluetoothRemoteGattServiceTest, MAYBE_GetCharacteristics_FindNone) { EXPECT_EQ(0u, service->GetCharacteristics().size()); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_GetCharacteristics_and_GetCharacteristic \ GetCharacteristics_and_GetCharacteristic #else #define MAYBE_GetCharacteristics_and_GetCharacteristic \ DISABLED_GetCharacteristics_and_GetCharacteristic #endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattServiceTestWinrt, + GetCharacteristics_and_GetCharacteristic) { +#else TEST_F(BluetoothRemoteGattServiceTest, MAYBE_GetCharacteristics_and_GetCharacteristic) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -191,6 +215,7 @@ TEST_F(BluetoothRemoteGattServiceTest, SimulateGattCharacteristic(service, kTestUUIDReconnectionAddress, /* properties */ 0); + base::RunLoop().RunUntilIdle(); // Verify that GetCharacteristic can retrieve characteristics again by ID, // and that the same Characteristics come back. EXPECT_EQ(4u, service->GetCharacteristics().size()); @@ -215,12 +240,16 @@ TEST_F(BluetoothRemoteGattServiceTest, service->GetCharacteristic(char_id1)); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_GetCharacteristicsByUUID GetCharacteristicsByUUID #else #define MAYBE_GetCharacteristicsByUUID DISABLED_GetCharacteristicsByUUID #endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattServiceTestWinrt, GetCharacteristicsByUUID) { +#else TEST_F(BluetoothRemoteGattServiceTest, MAYBE_GetCharacteristicsByUUID) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -247,6 +276,7 @@ TEST_F(BluetoothRemoteGattServiceTest, MAYBE_GetCharacteristicsByUUID) { /* properties */ 0); SimulateGattCharacteristic(service2, kTestUUIDHeartRateMeasurement, /* properties */ 0); + base::RunLoop().RunUntilIdle(); { std::vector<BluetoothRemoteGattCharacteristic*> characteristics = @@ -286,6 +316,7 @@ TEST_F(BluetoothRemoteGattServiceTest, MAYBE_GetCharacteristicsByUUID) { #define MAYBE_GattCharacteristics_ObserversCalls \ DISABLED_GattCharacteristics_ObserversCalls #endif +// The GattServicesRemoved event is not implemented for WinRT. TEST_F(BluetoothRemoteGattServiceTest, MAYBE_GattCharacteristics_ObserversCalls) { if (!PlatformSupportsLowEnergy()) { @@ -344,12 +375,16 @@ TEST_F(BluetoothRemoteGattServiceTest, EXPECT_EQ(0u, service->GetCharacteristics().size()); } -#if defined(OS_WIN) || defined(OS_MACOSX) +#if defined(OS_MACOSX) #define MAYBE_SimulateGattServiceRemove SimulateGattServiceRemove #else #define MAYBE_SimulateGattServiceRemove DISABLED_SimulateGattServiceRemove #endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattServiceTestWinrt, SimulateGattServiceRemove) { +#else TEST_F(BluetoothRemoteGattServiceTest, MAYBE_SimulateGattServiceRemove) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -368,6 +403,7 @@ TEST_F(BluetoothRemoteGattServiceTest, MAYBE_SimulateGattServiceRemove) { SimulateGattServicesDiscovered( device, std::vector<std::string>({kTestUUIDGenericAccess, kTestUUIDHeartRate})); + base::RunLoop().RunUntilIdle(); EXPECT_EQ(2u, device->GetGattServices().size()); // Simulate remove of a primary service. @@ -375,7 +411,13 @@ TEST_F(BluetoothRemoteGattServiceTest, MAYBE_SimulateGattServiceRemove) { BluetoothRemoteGattService* service2 = device->GetGattServices()[1]; std::string removed_service = service1->GetIdentifier(); SimulateGattServiceRemoved(device->GetGattService(removed_service)); - EXPECT_EQ(1, observer.gatt_service_removed_count()); + base::RunLoop().RunUntilIdle(); +#if defined(OS_WIN) + if (!GetParam()) { + // The GattServicesRemoved event is not implemented for WinRT. + EXPECT_EQ(1, observer.gatt_service_removed_count()); + } +#endif // defined(OS_WIN) EXPECT_EQ(1u, device->GetGattServices().size()); EXPECT_FALSE(device->GetGattService(removed_service)); EXPECT_EQ(device->GetGattServices()[0], service2); @@ -552,4 +594,11 @@ TEST_F(BluetoothRemoteGattServiceTest, ExtraDidDiscoverCharacteristicsCall) { } #endif // defined(OS_MACOSX) +#if defined(OS_WIN) +INSTANTIATE_TEST_CASE_P( + /* no prefix */, + BluetoothRemoteGattServiceTestWinrt, + ::testing::Bool()); +#endif // defined(OS_WIN) + } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_service_winrt.cc b/chromium/device/bluetooth/bluetooth_remote_gatt_service_winrt.cc index b2de385570f..0ee0e47ea11 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_service_winrt.cc +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_service_winrt.cc @@ -11,18 +11,15 @@ #include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/strings/stringprintf.h" -#include "base/win/scoped_hstring.h" #include "device/bluetooth/bluetooth_device.h" +#include "device/bluetooth/bluetooth_gatt_discoverer_winrt.h" namespace device { namespace { using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: - GattDeviceService; -using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: IGattDeviceService; -using ABI::Windows::Foundation::Collections::IVectorView; using Microsoft::WRL::ComPtr; } // namespace @@ -76,11 +73,74 @@ BluetoothRemoteGattServiceWinrt::GetIncludedServices() const { return {}; } +void BluetoothRemoteGattServiceWinrt::UpdateCharacteristics( + BluetoothGattDiscovererWinrt* gatt_discoverer) { + const auto* gatt_characteristics = + gatt_discoverer->GetCharacteristics(attribute_handle_); + DCHECK(gatt_characteristics); + + // Instead of clearing out |characteristics_| and creating each characteristic + // from scratch, we create a new map and move already existing characteristics + // into it in order to preserve pointer stability. + CharacteristicMap characteristics; + for (const auto& gatt_characteristic : *gatt_characteristics) { + auto characteristic = BluetoothRemoteGattCharacteristicWinrt::Create( + this, gatt_characteristic.Get()); + if (!characteristic) + continue; + + std::string identifier = characteristic->GetIdentifier(); + auto iter = characteristics_.find(identifier); + if (iter != characteristics_.end()) { + iter = characteristics.emplace(std::move(*iter)).first; + } else { + iter = characteristics + .emplace(std::move(identifier), std::move(characteristic)) + .first; + } + + static_cast<BluetoothRemoteGattCharacteristicWinrt*>(iter->second.get()) + ->UpdateDescriptors(gatt_discoverer); + } + + std::swap(characteristics, characteristics_); + SetDiscoveryComplete(true); +} + +IGattDeviceService* +BluetoothRemoteGattServiceWinrt::GetDeviceServiceForTesting() { + return gatt_service_.Get(); +} + +// static +uint8_t BluetoothRemoteGattServiceWinrt::ToProtocolError( + GattErrorCode error_code) { + switch (error_code) { + case GATT_ERROR_UNKNOWN: + return 0xF0; + case GATT_ERROR_FAILED: + return 0x01; + case GATT_ERROR_IN_PROGRESS: + return 0x09; + case GATT_ERROR_INVALID_LENGTH: + return 0x0D; + case GATT_ERROR_NOT_PERMITTED: + return 0x02; + case GATT_ERROR_NOT_AUTHORIZED: + return 0x08; + case GATT_ERROR_NOT_PAIRED: + return 0x0F; + case GATT_ERROR_NOT_SUPPORTED: + return 0x06; + } + + NOTREACHED(); + return 0x00; +} + BluetoothRemoteGattServiceWinrt::BluetoothRemoteGattServiceWinrt( BluetoothDevice* device, - Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth:: - GenericAttributeProfile::IGattDeviceService> - gatt_service, + ComPtr<IGattDeviceService> gatt_service, BluetoothUUID uuid, uint16_t attribute_handle) : device_(device), @@ -90,6 +150,5 @@ BluetoothRemoteGattServiceWinrt::BluetoothRemoteGattServiceWinrt( identifier_(base::StringPrintf("%s/%s_%04x", device_->GetIdentifier().c_str(), uuid_.value().c_str(), - attribute_handle_)) {} - + attribute_handle)) {} } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_service_winrt.h b/chromium/device/bluetooth/bluetooth_remote_gatt_service_winrt.h index c04a73afef0..e8ea154df5e 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_service_winrt.h +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_service_winrt.h @@ -8,18 +8,22 @@ #include <windows.devices.bluetooth.genericattributeprofile.h> #include <wrl/client.h> +#include <stdint.h> + #include <memory> #include <string> #include <vector> #include "base/macros.h" #include "device/bluetooth/bluetooth_export.h" +#include "device/bluetooth/bluetooth_remote_gatt_characteristic_winrt.h" #include "device/bluetooth/bluetooth_remote_gatt_service.h" #include "device/bluetooth/bluetooth_uuid.h" namespace device { class BluetoothDevice; +class BluetoothGattDiscovererWinrt; class DEVICE_BLUETOOTH_EXPORT BluetoothRemoteGattServiceWinrt : public BluetoothRemoteGattService { @@ -38,6 +42,81 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothRemoteGattServiceWinrt BluetoothDevice* GetDevice() const override; std::vector<BluetoothRemoteGattService*> GetIncludedServices() const override; + void UpdateCharacteristics(BluetoothGattDiscovererWinrt* gatt_discoverer); + + ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattDeviceService* + GetDeviceServiceForTesting(); + + template <typename Interface> + static GattErrorCode GetGattErrorCode(Interface* i) { + Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IReference<uint8_t>> + protocol_error_ref; + HRESULT hr = i->get_ProtocolError(&protocol_error_ref); + if (FAILED(hr)) { + VLOG(2) << "Getting Protocol Error Reference failed: " + << logging::SystemErrorCodeToString(hr); + return GattErrorCode::GATT_ERROR_UNKNOWN; + } + + if (!protocol_error_ref) { + VLOG(2) << "Got Null Protocol Error Reference."; + return GattErrorCode::GATT_ERROR_UNKNOWN; + } + + uint8_t protocol_error; + hr = protocol_error_ref->get_Value(&protocol_error); + if (FAILED(hr)) { + VLOG(2) << "Getting Protocol Error Value failed: " + << logging::SystemErrorCodeToString(hr); + return GattErrorCode::GATT_ERROR_UNKNOWN; + } + + VLOG(2) << "Got Protocol Error: " << static_cast<int>(protocol_error); + + // GATT Protocol Errors are described in the Bluetooth Core Specification + // Version 5.0 Vol 3, Part F, 3.4.1.1. + switch (protocol_error) { + case 0x01: // Invalid Handle + return GATT_ERROR_FAILED; + case 0x02: // Read Not Permitted + return GATT_ERROR_NOT_PERMITTED; + case 0x03: // Write Not Permitted + return GATT_ERROR_NOT_PERMITTED; + case 0x04: // Invalid PDU + return GATT_ERROR_FAILED; + case 0x05: // Insufficient Authentication + return GATT_ERROR_NOT_AUTHORIZED; + case 0x06: // Request Not Supported + return GATT_ERROR_NOT_SUPPORTED; + case 0x07: // Invalid Offset + return GATT_ERROR_INVALID_LENGTH; + case 0x08: // Insufficient Authorization + return GATT_ERROR_NOT_AUTHORIZED; + case 0x09: // Prepare Queue Full + return GATT_ERROR_IN_PROGRESS; + case 0x0A: // Attribute Not Found + return GATT_ERROR_FAILED; + case 0x0B: // Attribute Not Long + return GATT_ERROR_FAILED; + case 0x0C: // Insufficient Encryption Key Size + return GATT_ERROR_FAILED; + case 0x0D: // Invalid Attribute Value Length + return GATT_ERROR_INVALID_LENGTH; + case 0x0E: // Unlikely Error + return GATT_ERROR_FAILED; + case 0x0F: // Insufficient Encryption + return GATT_ERROR_NOT_PAIRED; + case 0x10: // Unsupported Group Type + return GATT_ERROR_FAILED; + case 0x11: // Insufficient Resources + return GATT_ERROR_FAILED; + default: + return GATT_ERROR_UNKNOWN; + } + } + + static uint8_t ToProtocolError(GattErrorCode error_code); + private: BluetoothRemoteGattServiceWinrt( BluetoothDevice* device, diff --git a/chromium/device/bluetooth/bluetooth_task_manager_win.cc b/chromium/device/bluetooth/bluetooth_task_manager_win.cc index 2f9f7aeba3a..665c0637b5c 100644 --- a/chromium/device/bluetooth/bluetooth_task_manager_win.cc +++ b/chromium/device/bluetooth/bluetooth_task_manager_win.cc @@ -17,7 +17,7 @@ #include "base/sequenced_task_runner.h" #include "base/strings/stringprintf.h" #include "base/strings/sys_string_conversions.h" -#include "base/task_scheduler/post_task.h" +#include "base/task/post_task.h" #include "device/bluetooth/bluetooth_classic_win.h" #include "device/bluetooth/bluetooth_device.h" #include "device/bluetooth/bluetooth_init_win.h" @@ -269,7 +269,7 @@ void BluetoothTaskManagerWin::RemoveObserver(Observer* observer) { void BluetoothTaskManagerWin::Initialize() { DCHECK(ui_task_runner_->RunsTasksInCurrentSequence()); InitializeWithBluetoothTaskRunner(base::CreateSequencedTaskRunnerWithTraits( - {base::MayBlock(), base::TaskPriority::BACKGROUND, + {base::MayBlock(), base::TaskPriority::BEST_EFFORT, base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})); } diff --git a/chromium/device/bluetooth/bluetooth_task_manager_win.h b/chromium/device/bluetooth/bluetooth_task_manager_win.h index 8a5e82f64d3..7dbbac12a63 100644 --- a/chromium/device/bluetooth/bluetooth_task_manager_win.h +++ b/chromium/device/bluetooth/bluetooth_task_manager_win.h @@ -333,7 +333,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothTaskManagerWin scoped_refptr<base::SequencedTaskRunner> bluetooth_task_runner_; // List of observers interested in event notifications. - base::ObserverList<Observer> observers_; + base::ObserverList<Observer>::Unchecked observers_; // indicates whether the adapter is in discovery mode or not. bool discovering_ = false; diff --git a/chromium/device/bluetooth/bluetooth_uuid.h b/chromium/device/bluetooth/bluetooth_uuid.h index c2624148357..36e21c0c6f9 100644 --- a/chromium/device/bluetooth/bluetooth_uuid.h +++ b/chromium/device/bluetooth/bluetooth_uuid.h @@ -60,7 +60,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothUUID { // value to them later. The default constructor will initialize an invalid // UUID by definition and the string accessors will return an empty string. BluetoothUUID(); - virtual ~BluetoothUUID(); + ~BluetoothUUID(); #if defined(OS_WIN) // The canonical UUID string format is device::BluetoothUUID.value(). diff --git a/chromium/device/bluetooth/bluetooth_uuid_unittest.cc b/chromium/device/bluetooth/bluetooth_uuid_unittest.cc index 04755198c66..647ff2846f9 100644 --- a/chromium/device/bluetooth/bluetooth_uuid_unittest.cc +++ b/chromium/device/bluetooth/bluetooth_uuid_unittest.cc @@ -106,7 +106,7 @@ TEST(BluetoothUUIDTest, GetCanonicalValueAsGUID) { const char kValid128Bit0[] = "12345678-1234-5678-9abc-def123456789"; GUID guid = BluetoothUUID::GetCanonicalValueAsGUID(kValid128Bit0); - EXPECT_EQ(0x12345678, guid.Data1); + EXPECT_EQ(0x12345678u, guid.Data1); EXPECT_EQ(0x1234, guid.Data2); EXPECT_EQ(0x5678, guid.Data3); EXPECT_EQ(0x9a, guid.Data4[0]); diff --git a/chromium/device/bluetooth/bluez/bluetooth_advertisement_bluez_unittest.cc b/chromium/device/bluetooth/bluez/bluetooth_advertisement_bluez_unittest.cc index 81518ba6020..f77cbb6419c 100644 --- a/chromium/device/bluetooth/bluez/bluetooth_advertisement_bluez_unittest.cc +++ b/chromium/device/bluetooth/bluez/bluetooth_advertisement_bluez_unittest.cc @@ -20,40 +20,16 @@ #include "device/bluetooth/dbus/bluez_dbus_manager.h" #include "device/bluetooth/dbus/fake_bluetooth_le_advertisement_service_provider.h" #include "device/bluetooth/dbus/fake_bluetooth_le_advertising_manager_client.h" +#include "device/bluetooth/test/test_bluetooth_advertisement_observer.h" #include "testing/gtest/include/gtest/gtest.h" using device::BluetoothAdapter; using device::BluetoothAdapterFactory; using device::BluetoothAdvertisement; +using device::TestBluetoothAdvertisementObserver; namespace bluez { -class TestAdvertisementObserver : public BluetoothAdvertisement::Observer { - public: - explicit TestAdvertisementObserver( - scoped_refptr<BluetoothAdvertisement> advertisement) - : released_(false), advertisement_(advertisement) { - advertisement_->AddObserver(this); - } - - ~TestAdvertisementObserver() override { - advertisement_->RemoveObserver(this); - } - - // BluetoothAdvertisement::Observer overrides: - void AdvertisementReleased(BluetoothAdvertisement* advertisement) override { - released_ = true; - } - - bool released() { return released_; } - - private: - bool released_; - scoped_refptr<BluetoothAdvertisement> advertisement_; - - DISALLOW_COPY_AND_ASSIGN(TestAdvertisementObserver); -}; - class BluetoothAdvertisementBlueZTest : public testing::Test { public: void SetUp() override { @@ -193,7 +169,7 @@ class BluetoothAdvertisementBlueZTest : public testing::Test { base::MessageLoopForIO message_loop_; - std::unique_ptr<TestAdvertisementObserver> observer_; + std::unique_ptr<TestBluetoothAdvertisementObserver> observer_; scoped_refptr<BluetoothAdapter> adapter_; scoped_refptr<BluetoothAdvertisement> advertisement_; }; @@ -257,7 +233,7 @@ TEST_F(BluetoothAdvertisementBlueZTest, UnregisterAfterReleasedFailed) { ExpectSuccess(); EXPECT_TRUE(advertisement); - observer_.reset(new TestAdvertisementObserver(advertisement)); + observer_.reset(new TestBluetoothAdvertisementObserver(advertisement)); TriggerReleased(advertisement); EXPECT_TRUE(observer_->released()); diff --git a/chromium/device/bluetooth/bluez/bluetooth_bluez_unittest.cc b/chromium/device/bluetooth/bluez/bluetooth_bluez_unittest.cc index 83ad64a235b..9962628a9fb 100644 --- a/chromium/device/bluetooth/bluez/bluetooth_bluez_unittest.cc +++ b/chromium/device/bluetooth/bluez/bluetooth_bluez_unittest.cc @@ -30,6 +30,7 @@ #include "device/bluetooth/dbus/fake_bluetooth_gatt_service_client.h" #include "device/bluetooth/dbus/fake_bluetooth_input_client.h" #include "device/bluetooth/test/test_bluetooth_adapter_observer.h" +#include "device/bluetooth/test/test_pairing_delegate.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/cros_system_api/dbus/service_constants.h" @@ -41,6 +42,7 @@ using device::BluetoothDiscoveryFilter; using device::BluetoothDiscoverySession; using device::BluetoothUUID; using device::TestBluetoothAdapterObserver; +using device::TestPairingDelegate; namespace bluez { @@ -87,88 +89,6 @@ class FakeBluetoothProfileServiceProviderDelegate } // namespace -class TestPairingDelegate : public BluetoothDevice::PairingDelegate { - public: - TestPairingDelegate() - : call_count_(0), - request_pincode_count_(0), - request_passkey_count_(0), - display_pincode_count_(0), - display_passkey_count_(0), - keys_entered_count_(0), - confirm_passkey_count_(0), - authorize_pairing_count_(0), - last_passkey_(9999999U), - last_entered_(999U) {} - ~TestPairingDelegate() override = default; - - void RequestPinCode(BluetoothDevice* device) override { - ++call_count_; - ++request_pincode_count_; - QuitMessageLoop(); - } - - void RequestPasskey(BluetoothDevice* device) override { - ++call_count_; - ++request_passkey_count_; - QuitMessageLoop(); - } - - void DisplayPinCode(BluetoothDevice* device, - const std::string& pincode) override { - ++call_count_; - ++display_pincode_count_; - last_pincode_ = pincode; - QuitMessageLoop(); - } - - void DisplayPasskey(BluetoothDevice* device, uint32_t passkey) override { - ++call_count_; - ++display_passkey_count_; - last_passkey_ = passkey; - QuitMessageLoop(); - } - - void KeysEntered(BluetoothDevice* device, uint32_t entered) override { - ++call_count_; - ++keys_entered_count_; - last_entered_ = entered; - QuitMessageLoop(); - } - - void ConfirmPasskey(BluetoothDevice* device, uint32_t passkey) override { - ++call_count_; - ++confirm_passkey_count_; - last_passkey_ = passkey; - QuitMessageLoop(); - } - - void AuthorizePairing(BluetoothDevice* device) override { - ++call_count_; - ++authorize_pairing_count_; - QuitMessageLoop(); - } - - int call_count_; - int request_pincode_count_; - int request_passkey_count_; - int display_pincode_count_; - int display_passkey_count_; - int keys_entered_count_; - int confirm_passkey_count_; - int authorize_pairing_count_; - uint32_t last_passkey_; - uint32_t last_entered_; - std::string last_pincode_; - - private: - // Some tests use a message loop since background processing is simulated; - // break out of those loops. - void QuitMessageLoop() { - if (base::RunLoop::IsRunningOnCurrentThread()) - base::RunLoop::QuitCurrentWhenIdleDeprecated(); - } -}; class BluetoothBlueZTest : public testing::Test { public: @@ -2839,6 +2759,43 @@ TEST_F(BluetoothBlueZTest, ConnectDeviceFails) { EXPECT_EQ(1, fake_bluetooth_adapter_client_->GetUnpauseCount()); } +// Tests that discovery is unpaused if the device gets removed during a +// connection. +TEST_F(BluetoothBlueZTest, RemoveDeviceDuringConnection) { + GetAdapter(); + + BluetoothDevice* device = adapter_->GetDevice( + bluez::FakeBluetoothDeviceClient::kPairedDeviceAddress); + ASSERT_TRUE(device != nullptr); + + fake_bluetooth_device_client_->LeaveConnectionsPending(); + device->Connect(nullptr, GetCallback(), + base::Bind(&BluetoothBlueZTest::ConnectErrorCallback, + base::Unretained(this))); + // We pause discovery before connecting. + EXPECT_EQ(1, fake_bluetooth_adapter_client_->GetPauseCount()); + EXPECT_EQ(0, fake_bluetooth_adapter_client_->GetUnpauseCount()); + + EXPECT_EQ(0, callback_count_); + EXPECT_EQ(0, error_callback_count_); + + EXPECT_FALSE(device->IsConnected()); + EXPECT_TRUE(device->IsConnecting()); + + // Install an observer; expect the DeviceRemoved method to be called + // with the device we remove. + TestBluetoothAdapterObserver observer(adapter_); + + device->Forget(base::DoNothing(), GetErrorCallback()); + EXPECT_EQ(0, error_callback_count_); + + EXPECT_EQ(1, observer.device_removed_count()); + + // If the device gets removed, we should still unpause discovery. + EXPECT_EQ(1, fake_bluetooth_adapter_client_->GetPauseCount()); + EXPECT_EQ(1, fake_bluetooth_adapter_client_->GetUnpauseCount()); +} + TEST_F(BluetoothBlueZTest, DisconnectDevice) { GetAdapter(); diff --git a/chromium/device/bluetooth/bluez/bluetooth_device_bluez.cc b/chromium/device/bluetooth/bluez/bluetooth_device_bluez.cc index 6f3612d1909..723bb6c4d7f 100644 --- a/chromium/device/bluetooth/bluez/bluetooth_device_bluez.cc +++ b/chromium/device/bluetooth/bluez/bluetooth_device_bluez.cc @@ -200,6 +200,26 @@ BluetoothDeviceBlueZ::~BluetoothDeviceBlueZ() { adapter()->NotifyGattServiceRemoved( static_cast<BluetoothRemoteGattServiceBlueZ*>(iter.second.get())); } + + // We pause discovery when trying to connect. Ensure discovery is unpaused if + // we get destroyed during a pending connection. + if (IsConnecting()) { + BLUETOOTH_LOG(EVENT) << object_path_.value() + << ": Unpausing discovery. Device removed."; + // Temporarily unpause discovery manually instead of using + // UnpauseDiscovery() which was introduced after branch point. + // TODO(ortuno): Remove once this is merged to M70. + bluez::BluezDBusManager::Get() + ->GetBluetoothAdapterClient() + ->UnpauseDiscovery( + adapter()->object_path(), base::Bind([]() { + BLUETOOTH_LOG(EVENT) << "Successfully un-paused discovery"; + }), + base::Bind([](const std::string& error_name, + const std::string& error_message) { + BLUETOOTH_LOG(EVENT) << "Failed to un-pause discovery"; + })); + } } uint32_t BluetoothDeviceBlueZ::GetBluetoothClass() const { diff --git a/chromium/device/bluetooth/cast/bluetooth_adapter_cast.cc b/chromium/device/bluetooth/cast/bluetooth_adapter_cast.cc index c95719c7a84..d0cb1d5a47f 100644 --- a/chromium/device/bluetooth/cast/bluetooth_adapter_cast.cc +++ b/chromium/device/bluetooth/cast/bluetooth_adapter_cast.cc @@ -9,7 +9,7 @@ #include "base/bind.h" #include "base/location.h" #include "base/no_destructor.h" -#include "base/task_scheduler/post_task.h" +#include "base/task/post_task.h" #include "base/threading/sequenced_task_runner_handle.h" #include "chromecast/device/bluetooth/bluetooth_util.h" #include "chromecast/device/bluetooth/le/gatt_client_manager.h" diff --git a/chromium/device/bluetooth/dbus/bluetooth_adapter_client.cc b/chromium/device/bluetooth/dbus/bluetooth_adapter_client.cc index bf25dfa70a4..20f65b96f6c 100644 --- a/chromium/device/bluetooth/dbus/bluetooth_adapter_client.cc +++ b/chromium/device/bluetooth/dbus/bluetooth_adapter_client.cc @@ -540,7 +540,7 @@ class BluetoothAdapterClientImpl : public BluetoothAdapterClient, dbus::ObjectManager* object_manager_; // List of observers interested in event notifications from us. - base::ObserverList<BluetoothAdapterClient::Observer> observers_; + base::ObserverList<BluetoothAdapterClient::Observer>::Unchecked observers_; // Weak pointer factory for generating 'this' pointers that might live longer // than we do. diff --git a/chromium/device/bluetooth/dbus/bluetooth_device_client.cc b/chromium/device/bluetooth/dbus/bluetooth_device_client.cc index 4ca96ede4dc..5167d69049d 100644 --- a/chromium/device/bluetooth/dbus/bluetooth_device_client.cc +++ b/chromium/device/bluetooth/dbus/bluetooth_device_client.cc @@ -650,7 +650,7 @@ class BluetoothDeviceClientImpl : public BluetoothDeviceClient, dbus::ObjectManager* object_manager_; // List of observers interested in event notifications from us. - base::ObserverList<BluetoothDeviceClient::Observer> observers_; + base::ObserverList<BluetoothDeviceClient::Observer>::Unchecked observers_; // Weak pointer factory for generating 'this' pointers that might live longer // than we do. diff --git a/chromium/device/bluetooth/dbus/bluetooth_gatt_characteristic_client.cc b/chromium/device/bluetooth/dbus/bluetooth_gatt_characteristic_client.cc index 22ab4de9416..fc3b0c9ab1c 100644 --- a/chromium/device/bluetooth/dbus/bluetooth_gatt_characteristic_client.cc +++ b/chromium/device/bluetooth/dbus/bluetooth_gatt_characteristic_client.cc @@ -321,7 +321,8 @@ class BluetoothGattCharacteristicClientImpl dbus::ObjectManager* object_manager_; // List of observers interested in event notifications from us. - base::ObserverList<BluetoothGattCharacteristicClient::Observer> observers_; + base::ObserverList<BluetoothGattCharacteristicClient::Observer>::Unchecked + observers_; // Weak pointer factory for generating 'this' pointers that might live longer // than we do. diff --git a/chromium/device/bluetooth/dbus/bluetooth_gatt_characteristic_service_provider_impl.cc b/chromium/device/bluetooth/dbus/bluetooth_gatt_characteristic_service_provider_impl.cc index 813f11eea21..0df8b9c48b3 100644 --- a/chromium/device/bluetooth/dbus/bluetooth_gatt_characteristic_service_provider_impl.cc +++ b/chromium/device/bluetooth/dbus/bluetooth_gatt_characteristic_service_provider_impl.cc @@ -373,8 +373,7 @@ void BluetoothGattCharacteristicServiceProviderImpl::PrepareWriteValue( it = options.find(bluetooth_gatt_characteristic::kOptionOffset); if (it != options.end()) it->second.PopUint16(&offset); - // TODO(b/78650442): kOptionHasSubsequentWrite - it = options.find("has-subsequent-write"); + it = options.find(bluetooth_gatt_characteristic::kOptionHasSubsequentWrite); if (it != options.end()) it->second.PopBool(&has_subsequent_write); diff --git a/chromium/device/bluetooth/dbus/bluetooth_gatt_descriptor_client.cc b/chromium/device/bluetooth/dbus/bluetooth_gatt_descriptor_client.cc index 685f4535628..a203429d25f 100644 --- a/chromium/device/bluetooth/dbus/bluetooth_gatt_descriptor_client.cc +++ b/chromium/device/bluetooth/dbus/bluetooth_gatt_descriptor_client.cc @@ -242,7 +242,8 @@ class BluetoothGattDescriptorClientImpl dbus::ObjectManager* object_manager_; // List of observers interested in event notifications from us. - base::ObserverList<BluetoothGattDescriptorClient::Observer> observers_; + base::ObserverList<BluetoothGattDescriptorClient::Observer>::Unchecked + observers_; // Weak pointer factory for generating 'this' pointers that might live longer // than we do. diff --git a/chromium/device/bluetooth/dbus/bluetooth_gatt_service_client.cc b/chromium/device/bluetooth/dbus/bluetooth_gatt_service_client.cc index f4178a4fa71..232ac02aefb 100644 --- a/chromium/device/bluetooth/dbus/bluetooth_gatt_service_client.cc +++ b/chromium/device/bluetooth/dbus/bluetooth_gatt_service_client.cc @@ -120,7 +120,8 @@ class BluetoothGattServiceClientImpl : public BluetoothGattServiceClient, dbus::ObjectManager* object_manager_; // List of observers interested in event notifications from us. - base::ObserverList<BluetoothGattServiceClient::Observer> observers_; + base::ObserverList<BluetoothGattServiceClient::Observer>::Unchecked + observers_; // Weak pointer factory for generating 'this' pointers that might live longer // than we do. diff --git a/chromium/device/bluetooth/dbus/bluetooth_input_client.cc b/chromium/device/bluetooth/dbus/bluetooth_input_client.cc index b909b0cb205..4b8d8f9f078 100644 --- a/chromium/device/bluetooth/dbus/bluetooth_input_client.cc +++ b/chromium/device/bluetooth/dbus/bluetooth_input_client.cc @@ -108,7 +108,7 @@ class BluetoothInputClientImpl : public BluetoothInputClient, dbus::ObjectManager* object_manager_; // List of observers interested in event notifications from us. - base::ObserverList<BluetoothInputClient::Observer> observers_; + base::ObserverList<BluetoothInputClient::Observer>::Unchecked observers_; // Weak pointer factory for generating 'this' pointers that might live longer // than we do. diff --git a/chromium/device/bluetooth/dbus/bluetooth_le_advertising_manager_client.cc b/chromium/device/bluetooth/dbus/bluetooth_le_advertising_manager_client.cc index 58e9042996f..3af99b95ae7 100644 --- a/chromium/device/bluetooth/dbus/bluetooth_le_advertising_manager_client.cc +++ b/chromium/device/bluetooth/dbus/bluetooth_le_advertising_manager_client.cc @@ -202,7 +202,8 @@ class BluetoothAdvertisementManagerClientImpl dbus::ObjectManager* object_manager_; // List of observers interested in event notifications from us. - base::ObserverList<BluetoothLEAdvertisingManagerClient::Observer> observers_; + base::ObserverList<BluetoothLEAdvertisingManagerClient::Observer>::Unchecked + observers_; // Weak pointer factory for generating 'this' pointers that might live longer // than we do. diff --git a/chromium/device/bluetooth/dbus/bluetooth_media_client.cc b/chromium/device/bluetooth/dbus/bluetooth_media_client.cc index cfa2004f846..39f31d8bedb 100644 --- a/chromium/device/bluetooth/dbus/bluetooth_media_client.cc +++ b/chromium/device/bluetooth/dbus/bluetooth_media_client.cc @@ -208,7 +208,7 @@ class BluetoothMediaClientImpl : public BluetoothMediaClient, dbus::ObjectManager* object_manager_; // List of observers interested in event notifications from us. - base::ObserverList<BluetoothMediaClient::Observer> observers_; + base::ObserverList<BluetoothMediaClient::Observer>::Unchecked observers_; base::WeakPtrFactory<BluetoothMediaClientImpl> weak_ptr_factory_; diff --git a/chromium/device/bluetooth/dbus/bluetooth_media_transport_client.cc b/chromium/device/bluetooth/dbus/bluetooth_media_transport_client.cc index 23eadd067d1..942b508ea81 100644 --- a/chromium/device/bluetooth/dbus/bluetooth_media_transport_client.cc +++ b/chromium/device/bluetooth/dbus/bluetooth_media_transport_client.cc @@ -270,7 +270,8 @@ class BluetoothMediaTransportClientImpl dbus::ObjectManager* object_manager_; // List of observers interested in event notifications from us. - base::ObserverList<BluetoothMediaTransportClient::Observer> observers_; + base::ObserverList<BluetoothMediaTransportClient::Observer>::Unchecked + observers_; base::WeakPtrFactory<BluetoothMediaTransportClientImpl> weak_ptr_factory_; diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_adapter_client.h b/chromium/device/bluetooth/dbus/fake_bluetooth_adapter_client.h index 3c4257d3867..59adc334a6a 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_adapter_client.h +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_adapter_client.h @@ -115,7 +115,7 @@ class DEVICE_BLUETOOTH_EXPORT FakeBluetoothAdapterClient void PostDelayedTask(base::OnceClosure callback); // List of observers interested in event notifications from us. - base::ObserverList<Observer> observers_; + base::ObserverList<Observer>::Unchecked observers_; // Static properties we return. std::unique_ptr<Properties> properties_; diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_device_client.cc b/chromium/device/bluetooth/dbus/fake_bluetooth_device_client.cc index 8dfcd20fd17..bfa2304ea18 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_device_client.cc +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_device_client.cc @@ -25,7 +25,7 @@ #include "base/single_thread_task_runner.h" #include "base/stl_util.h" #include "base/strings/string_util.h" -#include "base/task_scheduler/post_task.h" +#include "base/task/post_task.h" #include "base/threading/thread_task_runner_handle.h" #include "base/time/time.h" #include "device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.h" @@ -318,7 +318,8 @@ FakeBluetoothDeviceClient::FakeBluetoothDeviceClient() connection_rssi_(kUnkownPower), transmit_power_(kUnkownPower), max_transmit_power_(kUnkownPower), - delay_start_discovery_(false) { + delay_start_discovery_(false), + should_leave_connections_pending_(false) { std::unique_ptr<Properties> properties(new Properties( base::Bind(&FakeBluetoothDeviceClient::OnPropertyChanged, base::Unretained(this), dbus::ObjectPath(kPairedDevicePath)))); @@ -367,6 +368,10 @@ FakeBluetoothDeviceClient::FakeBluetoothDeviceClient() FakeBluetoothDeviceClient::~FakeBluetoothDeviceClient() = default; +void FakeBluetoothDeviceClient::LeaveConnectionsPending() { + should_leave_connections_pending_ = true; +} + void FakeBluetoothDeviceClient::Init( dbus::Bus* bus, const std::string& bluetooth_service_name) {} @@ -418,6 +423,9 @@ void FakeBluetoothDeviceClient::Connect(const dbus::ObjectPath& object_path, return; } + if (should_leave_connections_pending_) + return; + if (properties->paired.value() != true && object_path != dbus::ObjectPath(kConnectUnpairablePath) && object_path != dbus::ObjectPath(kLowEnergyPath)) { diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_device_client.h b/chromium/device/bluetooth/dbus/fake_bluetooth_device_client.h index 0b0d8e44f65..b483909a9e4 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_device_client.h +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_device_client.h @@ -72,6 +72,9 @@ class DEVICE_BLUETOOTH_EXPORT FakeBluetoothDeviceClient FakeBluetoothDeviceClient(); ~FakeBluetoothDeviceClient() override; + // Causes Connect() calls to never finish. + void LeaveConnectionsPending(); + // BluetoothDeviceClient overrides void Init(dbus::Bus* bus, const std::string& bluetooth_service_name) override; void AddObserver(Observer* observer) override; @@ -349,7 +352,7 @@ class DEVICE_BLUETOOTH_EXPORT FakeBluetoothDeviceClient BluetoothProfileServiceProvider::Delegate::Status status); // List of observers interested in event notifications from us. - base::ObserverList<Observer> observers_; + base::ObserverList<Observer>::Unchecked observers_; using PropertiesMap = std::map<const dbus::ObjectPath, std::unique_ptr<Properties>>; @@ -378,6 +381,8 @@ class DEVICE_BLUETOOTH_EXPORT FakeBluetoothDeviceClient // Pending prepare write requests. std::vector<std::pair<dbus::ObjectPath, std::vector<uint8_t>>> prepare_write_requests_; + + bool should_leave_connections_pending_; }; } // namespace bluez diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_characteristic_client.h b/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_characteristic_client.h index dbc73e38e04..126bcb7b042 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_characteristic_client.h +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_characteristic_client.h @@ -195,7 +195,7 @@ class DEVICE_BLUETOOTH_EXPORT FakeBluetoothGattCharacteristicClient std::map<std::string, DelayedCallback*> action_extra_requests_; // List of observers interested in event notifications from us. - base::ObserverList<Observer> observers_; + base::ObserverList<Observer>::Unchecked observers_; // Weak pointer factory for generating 'this' pointers that might live longer // than we do. diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_descriptor_client.h b/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_descriptor_client.h index d67fec623c2..4f22a685cda 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_descriptor_client.h +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_descriptor_client.h @@ -91,7 +91,7 @@ class DEVICE_BLUETOOTH_EXPORT FakeBluetoothGattDescriptorClient PropertiesMap properties_; // List of observers interested in event notifications from us. - base::ObserverList<Observer> observers_; + base::ObserverList<Observer>::Unchecked observers_; // Weak pointer factory for generating 'this' pointers that might live longer // than we do. diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_service_client.h b/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_service_client.h index 0969fe70250..cac2a56d2c4 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_service_client.h +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_service_client.h @@ -114,7 +114,7 @@ class DEVICE_BLUETOOTH_EXPORT FakeBluetoothGattServiceClient std::string battery_service_path_; // List of observers interested in event notifications from us. - base::ObserverList<Observer> observers_; + base::ObserverList<Observer>::Unchecked observers_; // Weak pointer factory for generating 'this' pointers that might live longer // than we do. diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_input_client.h b/chromium/device/bluetooth/dbus/fake_bluetooth_input_client.h index a513db0670b..b4bf3db0165 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_input_client.h +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_input_client.h @@ -57,7 +57,7 @@ class DEVICE_BLUETOOTH_EXPORT FakeBluetoothInputClient std::map<const dbus::ObjectPath, std::unique_ptr<Properties>> properties_map_; // List of observers interested in event notifications from us. - base::ObserverList<Observer> observers_; + base::ObserverList<Observer>::Unchecked observers_; DISALLOW_COPY_AND_ASSIGN(FakeBluetoothInputClient); }; diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_media_client.h b/chromium/device/bluetooth/dbus/fake_bluetooth_media_client.h index 346a2f29c59..99a3e118fdb 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_media_client.h +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_media_client.h @@ -70,7 +70,7 @@ class DEVICE_BLUETOOTH_EXPORT FakeBluetoothMediaClient endpoints_; // List of observers interested in event notifications from us. - base::ObserverList<BluetoothMediaClient::Observer> observers_; + base::ObserverList<BluetoothMediaClient::Observer>::Unchecked observers_; DISALLOW_COPY_AND_ASSIGN(FakeBluetoothMediaClient); }; diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_media_transport_client.h b/chromium/device/bluetooth/dbus/fake_bluetooth_media_transport_client.h index 1b62f7a394c..31fe4f25227 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_media_transport_client.h +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_media_transport_client.h @@ -142,7 +142,8 @@ class DEVICE_BLUETOOTH_EXPORT FakeBluetoothMediaTransportClient // corresponding endpoint path when GetProperties() is called. std::map<dbus::ObjectPath, dbus::ObjectPath> transport_to_endpoint_map_; - base::ObserverList<BluetoothMediaTransportClient::Observer> observers_; + base::ObserverList<BluetoothMediaTransportClient::Observer>::Unchecked + observers_; DISALLOW_COPY_AND_ASSIGN(FakeBluetoothMediaTransportClient); }; diff --git a/chromium/device/bluetooth/event_utils_winrt.h b/chromium/device/bluetooth/event_utils_winrt.h index ec032232a93..67c1ed213dc 100644 --- a/chromium/device/bluetooth/event_utils_winrt.h +++ b/chromium/device/bluetooth/event_utils_winrt.h @@ -123,7 +123,8 @@ HRESULT PostAsyncResults( // Convenience template function to construct a TypedEventHandler from a // base::RepeatingCallback of a matching signature. In case of success, the // EventRegistrationToken is returned to the caller. A return value of -// base::nullopt indicates a failure. +// base::nullopt indicates a failure. Events are posted to the same thread the +// event handler was created on. template <typename Interface, typename Sender, typename Args, @@ -133,17 +134,24 @@ base::Optional<EventRegistrationToken> AddTypedEventHandler( Interface* i, internal::IMemberFunction< Interface, - ABI::Windows::Foundation::ITypedEventHandler<Sender, Args>*, + ABI::Windows::Foundation::ITypedEventHandler<Sender*, Args*>*, EventRegistrationToken*> function, - base::RepeatingCallback<void(SenderAbi, ArgsAbi)> callback) { + base::RepeatingCallback<void(SenderAbi*, ArgsAbi*)> callback) { EventRegistrationToken token; HRESULT hr = ((*i).*function)( Microsoft::WRL::Callback< - ABI::Windows::Foundation::ITypedEventHandler<Sender, Args>>( - [callback](SenderAbi sender, ArgsAbi args) { - callback.Run(std::move(sender), std::move(args)); - return S_OK; - }) + ABI::Windows::Foundation::ITypedEventHandler<Sender*, Args*>>([ + task_runner(base::ThreadTaskRunnerHandle::Get()), + callback(std::move(callback)) + ](SenderAbi * sender, ArgsAbi * args) { + // Make sure we are still on the same thread. + DCHECK_EQ(base::ThreadTaskRunnerHandle::Get(), task_runner); + task_runner->PostTask( + FROM_HERE, + base::BindOnce(callback, Microsoft::WRL::ComPtr<SenderAbi>(sender), + Microsoft::WRL::ComPtr<ArgsAbi>(args))); + return S_OK; + }) .Get(), &token); diff --git a/chromium/device/fido/BUILD.gn b/chromium/device/fido/BUILD.gn index 369ac7d8522..c475300986d 100644 --- a/chromium/device/fido/BUILD.gn +++ b/chromium/device/fido/BUILD.gn @@ -27,6 +27,29 @@ component("fido") { "authenticator_selection_criteria.h", "authenticator_supported_options.cc", "authenticator_supported_options.h", + "ble/fido_ble_connection.cc", + "ble/fido_ble_connection.h", + "ble/fido_ble_device.cc", + "ble/fido_ble_device.h", + "ble/fido_ble_discovery.cc", + "ble/fido_ble_discovery.h", + "ble/fido_ble_discovery_base.cc", + "ble/fido_ble_discovery_base.h", + "ble/fido_ble_frames.cc", + "ble/fido_ble_frames.h", + "ble/fido_ble_transaction.cc", + "ble/fido_ble_transaction.h", + "ble/fido_ble_uuids.cc", + "ble/fido_ble_uuids.h", + "ble_adapter_power_manager.cc", + "ble_adapter_power_manager.h", + "cable/cable_discovery_data.h", + "cable/fido_cable_device.cc", + "cable/fido_cable_device.h", + "cable/fido_cable_discovery.cc", + "cable/fido_cable_discovery.h", + "cable/fido_cable_handshake_handler.cc", + "cable/fido_cable_handshake_handler.h", "ctap2_device_operation.h", "ctap_empty_authenticator_request.cc", "ctap_empty_authenticator_request.h", @@ -40,26 +63,6 @@ component("fido") { "ec_public_key.cc", "ec_public_key.h", "fido_authenticator.h", - "fido_ble_connection.cc", - "fido_ble_connection.h", - "fido_ble_device.cc", - "fido_ble_device.h", - "fido_ble_discovery.cc", - "fido_ble_discovery.h", - "fido_ble_discovery_base.cc", - "fido_ble_discovery_base.h", - "fido_ble_frames.cc", - "fido_ble_frames.h", - "fido_ble_transaction.cc", - "fido_ble_transaction.h", - "fido_ble_uuids.cc", - "fido_ble_uuids.h", - "fido_cable_device.cc", - "fido_cable_device.h", - "fido_cable_discovery.cc", - "fido_cable_discovery.h", - "fido_cable_handshake_handler.cc", - "fido_cable_handshake_handler.h", "fido_constants.cc", "fido_constants.h", "fido_device.cc", @@ -68,10 +71,6 @@ component("fido") { "fido_device_authenticator.h", "fido_discovery.cc", "fido_discovery.h", - "fido_hid_message.cc", - "fido_hid_message.h", - "fido_hid_packet.cc", - "fido_hid_packet.h", "fido_parsing_utils.cc", "fido_parsing_utils.h", "fido_request_handler.h", @@ -79,11 +78,16 @@ component("fido") { "fido_request_handler_base.h", "fido_task.cc", "fido_task.h", + "fido_transport_protocol.cc", "fido_transport_protocol.h", "get_assertion_request_handler.cc", "get_assertion_request_handler.h", "get_assertion_task.cc", "get_assertion_task.h", + "hid/fido_hid_message.cc", + "hid/fido_hid_message.h", + "hid/fido_hid_packet.cc", + "hid/fido_hid_packet.h", "make_credential_request_handler.cc", "make_credential_request_handler.h", "make_credential_task.cc", @@ -125,9 +129,11 @@ component("fido") { "//components/cbor", "//crypto", "//device/base", + "//device/fido/strings", "//services/service_manager/public/cpp", "//services/service_manager/public/mojom", "//third_party/boringssl", + "//ui/base", ] public_deps = [ @@ -141,10 +147,10 @@ component("fido") { # HID is not supported on Android. if (!is_android) { sources += [ - "fido_hid_device.cc", - "fido_hid_device.h", - "fido_hid_discovery.cc", - "fido_hid_discovery.h", + "hid/fido_hid_device.cc", + "hid/fido_hid_device.h", + "hid/fido_hid_discovery.cc", + "hid/fido_hid_discovery.h", ] deps += [ @@ -187,8 +193,8 @@ source_set("mocks") { testonly = true sources = [ - "mock_fido_ble_connection.cc", - "mock_fido_ble_connection.h", + "ble/mock_fido_ble_connection.cc", + "ble/mock_fido_ble_connection.h", "mock_fido_device.cc", "mock_fido_device.h", "mock_fido_discovery_observer.cc", @@ -205,7 +211,7 @@ source_set("mocks") { fuzzer_test("fido_hid_message_fuzzer") { sources = [ - "fido_hid_message_fuzzer.cc", + "hid/fido_hid_message_fuzzer.cc", ] deps = [ ":fido", @@ -216,7 +222,7 @@ fuzzer_test("fido_hid_message_fuzzer") { fuzzer_test("fido_ble_frames_fuzzer") { sources = [ - "fido_ble_frames_fuzzer.cc", + "ble/fido_ble_frames_fuzzer.cc", ] deps = [ ":fido", @@ -239,11 +245,14 @@ fuzzer_test("ctap_response_fuzzer") { fuzzer_test("fido_cable_handshake_handler_fuzzer") { sources = [ - "fido_cable_handshake_handler_fuzzer.cc", + "cable/fido_cable_handshake_handler_fuzzer.cc", ] deps = [ ":fido", "//base", + "//device/bluetooth:mocks", + "//testing/gmock", + "//testing/gtest", ] libfuzzer_options = [ "max_len=2048" ] } @@ -272,8 +281,19 @@ source_set("test_support") { # Android doesn't compile. Linux requires udev. if (!is_linux_without_udev && !is_android) { sources += [ - "fake_hid_impl_for_testing.cc", - "fake_hid_impl_for_testing.h", + "hid/fake_hid_impl_for_testing.cc", + "hid/fake_hid_impl_for_testing.h", + ] + } + + if (is_mac) { + sources += [ + "mac/fake_keychain.h", + "mac/fake_keychain.mm", + "mac/fake_touch_id_context.h", + "mac/fake_touch_id_context.mm", + "mac/scoped_touch_id_test_environment.h", + "mac/scoped_touch_id_test_environment.mm", ] } } diff --git a/chromium/device/fido/DEPS b/chromium/device/fido/DEPS index 37d845668ad..3d3a449eff9 100644 --- a/chromium/device/fido/DEPS +++ b/chromium/device/fido/DEPS @@ -4,5 +4,6 @@ include_rules = [ "+crypto", "+net/base", "+net/cert", + "+ui/base/l10n", "+third_party/boringssl/src/include", ] diff --git a/chromium/device/fido/attestation_object.cc b/chromium/device/fido/attestation_object.cc index 1090e09e9e4..217ee4d6c33 100644 --- a/chromium/device/fido/attestation_object.cc +++ b/chromium/device/fido/attestation_object.cc @@ -39,19 +39,20 @@ void AttestationObject::EraseAttestationStatement() { #if DCHECK_IS_ON() if (!authenticator_data_.attested_data()) return; - - std::vector<uint8_t> auth_data = authenticator_data_.SerializeToByteArray(); - // See diagram at https://w3c.github.io/webauthn/#sctn-attestation - constexpr size_t kAaguidOffset = - 32 /* RP ID hash */ + 1 /* flags */ + 4 /* signature counter */; - constexpr size_t kAaguidSize = 16; - DCHECK_GE(auth_data.size(), kAaguidOffset + kAaguidSize); - DCHECK(std::all_of(auth_data.data() + kAaguidOffset, - auth_data.data() + kAaguidOffset + kAaguidSize, - [](uint8_t v) { return v == 0; })); + DCHECK(authenticator_data_.attested_data()->IsAaguidZero()); #endif } +bool AttestationObject::IsSelfAttestation() { + if (!attestation_statement_->IsSelfAttestation()) { + return false; + } + // Self-attestation also requires that the AAGUID be zero. See + // https://www.w3.org/TR/webauthn/#createCredential. + return !authenticator_data_.attested_data() || + authenticator_data_.attested_data()->IsAaguidZero(); +} + bool AttestationObject::IsAttestationCertificateInappropriatelyIdentifying() { return attestation_statement_ ->IsAttestationCertificateInappropriatelyIdentifying(); diff --git a/chromium/device/fido/attestation_object.h b/chromium/device/fido/attestation_object.h index bde70e6d495..36f463d8493 100644 --- a/chromium/device/fido/attestation_object.h +++ b/chromium/device/fido/attestation_object.h @@ -41,6 +41,13 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AttestationObject { // https://w3c.github.io/webauthn/#createCredential. void EraseAttestationStatement(); + // Returns true if the attestation is a "self" attestation, i.e. is just the + // private key signing itself to show that it is fresh. See + // https://www.w3.org/TR/webauthn/#self-attestation. Note that self- + // attestation also requires at the AAGUID in the authenticator data be all + // zeros. + bool IsSelfAttestation(); + // Returns true if the attestation certificate is known to be inappropriately // identifying. Some tokens return unique attestation certificates even when // the bit to request that is not set. (Normal attestation certificates are diff --git a/chromium/device/fido/attestation_statement.cc b/chromium/device/fido/attestation_statement.cc index e03c4997685..fea6e30d3c7 100644 --- a/chromium/device/fido/attestation_statement.cc +++ b/chromium/device/fido/attestation_statement.cc @@ -26,6 +26,10 @@ bool NoneAttestationStatement:: return false; } +bool NoneAttestationStatement::IsSelfAttestation() { + return false; +} + cbor::CBORValue::MapValue NoneAttestationStatement::GetAsCBORMap() const { return cbor::CBORValue::MapValue(); } diff --git a/chromium/device/fido/attestation_statement.h b/chromium/device/fido/attestation_statement.h index f7e0621a0c8..2be4288d516 100644 --- a/chromium/device/fido/attestation_statement.h +++ b/chromium/device/fido/attestation_statement.h @@ -31,6 +31,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AttestationStatement { // nested within another CBOR object and encoded then. virtual cbor::CBORValue::MapValue GetAsCBORMap() const = 0; + // Returns true if the attestation is a "self" attestation, i.e. is just the + // private key signing itself to show that it is fresh. + virtual bool IsSelfAttestation() = 0; + // Returns true if the attestation is known to be inappropriately identifying. // Some tokens return unique attestation certificates even when the bit to // request that is not set. (Normal attestation certificates are not @@ -41,10 +45,9 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AttestationStatement { protected: explicit AttestationStatement(std::string format); - - private: const std::string format_; + private: DISALLOW_COPY_AND_ASSIGN(AttestationStatement); }; @@ -57,6 +60,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) NoneAttestationStatement NoneAttestationStatement(); ~NoneAttestationStatement() override; + bool IsSelfAttestation() override; bool IsAttestationCertificateInappropriatelyIdentifying() override; cbor::CBORValue::MapValue GetAsCBORMap() const override; diff --git a/chromium/device/fido/attestation_statement_formats.cc b/chromium/device/fido/attestation_statement_formats.cc index e3f19693aa7..5b7cde84784 100644 --- a/chromium/device/fido/attestation_statement_formats.cc +++ b/chromium/device/fido/attestation_statement_formats.cc @@ -135,6 +135,10 @@ cbor::CBORValue::MapValue FidoAttestationStatement::GetAsCBORMap() const { return attestation_statement_map; } +bool FidoAttestationStatement::IsSelfAttestation() { + return false; +} + bool FidoAttestationStatement:: IsAttestationCertificateInappropriatelyIdentifying() { // An attestation certificate is considered inappropriately identifying if it @@ -182,6 +186,10 @@ cbor::CBORValue::MapValue PackedAttestationStatement::GetAsCBORMap() const { return attestation_statement_map; } +bool PackedAttestationStatement::IsSelfAttestation() { + return x509_certificates_.empty(); +} + bool PackedAttestationStatement:: IsAttestationCertificateInappropriatelyIdentifying() { for (const auto& der_bytes : x509_certificates_) { diff --git a/chromium/device/fido/attestation_statement_formats.h b/chromium/device/fido/attestation_statement_formats.h index fb16d88cd40..64de004d600 100644 --- a/chromium/device/fido/attestation_statement_formats.h +++ b/chromium/device/fido/attestation_statement_formats.h @@ -31,6 +31,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoAttestationStatement // AttestationStatement cbor::CBORValue::MapValue GetAsCBORMap() const override; + bool IsSelfAttestation() override; bool IsAttestationCertificateInappropriatelyIdentifying() override; private: @@ -56,6 +57,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) PackedAttestationStatement // AttestationStatement cbor::CBORValue::MapValue GetAsCBORMap() const override; + bool IsSelfAttestation() override; bool IsAttestationCertificateInappropriatelyIdentifying() override; private: diff --git a/chromium/device/fido/attested_credential_data.cc b/chromium/device/fido/attested_credential_data.cc index 70614dfca93..5b93d8cd8d9 100644 --- a/chromium/device/fido/attested_credential_data.cc +++ b/chromium/device/fido/attested_credential_data.cc @@ -91,6 +91,11 @@ AttestedCredentialData& AttestedCredentialData::operator=( AttestedCredentialData::~AttestedCredentialData() = default; +bool AttestedCredentialData::IsAaguidZero() const { + return std::all_of(aaguid_.begin(), aaguid_.end(), + [](uint8_t v) { return v == 0; }); +} + void AttestedCredentialData::DeleteAaguid() { std::fill(aaguid_.begin(), aaguid_.end(), 0); } diff --git a/chromium/device/fido/attested_credential_data.h b/chromium/device/fido/attested_credential_data.h index 26048860c0d..73ed9f5e00a 100644 --- a/chromium/device/fido/attested_credential_data.h +++ b/chromium/device/fido/attested_credential_data.h @@ -38,6 +38,9 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AttestedCredentialData { const std::vector<uint8_t>& credential_id() const { return credential_id_; } + // Returns true iff the AAGUID is all zero bytes. + bool IsAaguidZero() const; + // Invoked when sending "none" attestation statement to the relying party. // Replaces AAGUID with zero bytes. void DeleteAaguid(); diff --git a/chromium/device/fido/authenticator_make_credential_response.cc b/chromium/device/fido/authenticator_make_credential_response.cc index fd6576e092e..bd79c114b20 100644 --- a/chromium/device/fido/authenticator_make_credential_response.cc +++ b/chromium/device/fido/authenticator_make_credential_response.cc @@ -78,6 +78,10 @@ void AuthenticatorMakeCredentialResponse::EraseAttestationStatement() { attestation_object_.EraseAttestationStatement(); } +bool AuthenticatorMakeCredentialResponse::IsSelfAttestation() { + return attestation_object_.IsSelfAttestation(); +} + bool AuthenticatorMakeCredentialResponse:: IsAttestationCertificateInappropriatelyIdentifying() { return attestation_object_ diff --git a/chromium/device/fido/authenticator_make_credential_response.h b/chromium/device/fido/authenticator_make_credential_response.h index dc9a6e7438a..27e1ed33d22 100644 --- a/chromium/device/fido/authenticator_make_credential_response.h +++ b/chromium/device/fido/authenticator_make_credential_response.h @@ -46,6 +46,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorMakeCredentialResponse // https://w3c.github.io/webauthn/#createCredential void EraseAttestationStatement(); + // Returns true if the attestation is a "self" attestation, i.e. is just the + // private key signing itself to show that it is fresh and the AAGUID is zero. + bool IsSelfAttestation(); + // Returns true if the attestation certificate is known to be inappropriately // identifying. Some tokens return unique attestation certificates even when // the bit to request that is not set. (Normal attestation certificates are diff --git a/chromium/device/fido/ble/fido_ble_connection.cc b/chromium/device/fido/ble/fido_ble_connection.cc new file mode 100644 index 00000000000..cb56324f4bc --- /dev/null +++ b/chromium/device/fido/ble/fido_ble_connection.cc @@ -0,0 +1,518 @@ +// 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 "device/fido/ble/fido_ble_connection.h" + +#include <algorithm> +#include <ostream> +#include <utility> + +#include "base/barrier_closure.h" +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread_task_runner_handle.h" +#include "build/build_config.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/bluetooth_uuid.h" +#include "device/fido/ble/fido_ble_uuids.h" + +namespace device { + +namespace { + +using ServiceRevisionsCallback = + base::OnceCallback<void(std::vector<FidoBleConnection::ServiceRevision>)>; + +constexpr const char* ToString(BluetoothDevice::ConnectErrorCode error_code) { + switch (error_code) { + case BluetoothDevice::ERROR_AUTH_CANCELED: + return "ERROR_AUTH_CANCELED"; + case BluetoothDevice::ERROR_AUTH_FAILED: + return "ERROR_AUTH_FAILED"; + case BluetoothDevice::ERROR_AUTH_REJECTED: + return "ERROR_AUTH_REJECTED"; + case BluetoothDevice::ERROR_AUTH_TIMEOUT: + return "ERROR_AUTH_TIMEOUT"; + case BluetoothDevice::ERROR_FAILED: + return "ERROR_FAILED"; + case BluetoothDevice::ERROR_INPROGRESS: + return "ERROR_INPROGRESS"; + case BluetoothDevice::ERROR_UNKNOWN: + return "ERROR_UNKNOWN"; + case BluetoothDevice::ERROR_UNSUPPORTED_DEVICE: + return "ERROR_UNSUPPORTED_DEVICE"; + default: + NOTREACHED(); + return ""; + } +} + +constexpr const char* ToString(BluetoothGattService::GattErrorCode error_code) { + switch (error_code) { + case BluetoothGattService::GATT_ERROR_UNKNOWN: + return "GATT_ERROR_UNKNOWN"; + case BluetoothGattService::GATT_ERROR_FAILED: + return "GATT_ERROR_FAILED"; + case BluetoothGattService::GATT_ERROR_IN_PROGRESS: + return "GATT_ERROR_IN_PROGRESS"; + case BluetoothGattService::GATT_ERROR_INVALID_LENGTH: + return "GATT_ERROR_INVALID_LENGTH"; + case BluetoothGattService::GATT_ERROR_NOT_PERMITTED: + return "GATT_ERROR_NOT_PERMITTED"; + case BluetoothGattService::GATT_ERROR_NOT_AUTHORIZED: + return "GATT_ERROR_NOT_AUTHORIZED"; + case BluetoothGattService::GATT_ERROR_NOT_PAIRED: + return "GATT_ERROR_NOT_PAIRED"; + case BluetoothGattService::GATT_ERROR_NOT_SUPPORTED: + return "GATT_ERROR_NOT_SUPPORTED"; + default: + NOTREACHED(); + return ""; + } +} + +std::ostream& operator<<(std::ostream& os, + FidoBleConnection::ServiceRevision revision) { + switch (revision) { + case FidoBleConnection::ServiceRevision::kU2f11: + return os << "U2F 1.1"; + case FidoBleConnection::ServiceRevision::kU2f12: + return os << "U2F 1.2"; + case FidoBleConnection::ServiceRevision::kFido2: + return os << "FIDO2"; + } + + NOTREACHED(); + return os; +} + +const BluetoothRemoteGattService* GetFidoService( + const BluetoothDevice* device) { + if (!device) { + LOG(ERROR) << "No device present."; + return nullptr; + } + + for (const auto* service : device->GetGattServices()) { + if (service->GetUUID() == BluetoothUUID(kFidoServiceUUID)) + return service; + } + + LOG(ERROR) << "No Fido service present."; + return nullptr; +} + +void OnWriteRemoteCharacteristic(FidoBleConnection::WriteCallback callback) { + VLOG(2) << "Writing Remote Characteristic Succeeded."; + std::move(callback).Run(true); +} + +void OnWriteRemoteCharacteristicError( + FidoBleConnection::WriteCallback callback, + BluetoothGattService::GattErrorCode error_code) { + LOG(ERROR) << "Writing Remote Characteristic Failed: " + << ToString(error_code); + std::move(callback).Run(false); +} + +void OnReadServiceRevisionBitfield(ServiceRevisionsCallback callback, + const std::vector<uint8_t>& value) { + if (value.empty()) { + VLOG(2) << "Service Revision Bitfield is empty."; + std::move(callback).Run({}); + return; + } + + if (value.size() != 1u) { + VLOG(2) << "Service Revision Bitfield has unexpected size: " << value.size() + << ". Ignoring all but the first byte."; + } + + const uint8_t bitset = value[0]; + if (bitset & 0x1F) { + VLOG(2) << "Service Revision Bitfield has unexpected bits set: " + << base::StringPrintf("0x%02X", bitset) + << ". Ignoring all but the first three bits."; + } + + std::vector<FidoBleConnection::ServiceRevision> service_revisions; + for (auto revision : {FidoBleConnection::ServiceRevision::kU2f11, + FidoBleConnection::ServiceRevision::kU2f12, + FidoBleConnection::ServiceRevision::kFido2}) { + if (bitset & static_cast<uint8_t>(revision)) { + VLOG(2) << "Detected Support for " << revision << "."; + service_revisions.push_back(revision); + } + } + + std::move(callback).Run(std::move(service_revisions)); +} + +void OnReadServiceRevisionBitfieldError( + ServiceRevisionsCallback callback, + BluetoothGattService::GattErrorCode error_code) { + LOG(ERROR) << "Error while reading Service Revision Bitfield: " + << ToString(error_code); + std::move(callback).Run({}); +} + +} // namespace + +FidoBleConnection::FidoBleConnection( + BluetoothAdapter* adapter, + std::string device_address, + ReadCallback read_callback) + : adapter_(adapter), + address_(std::move(device_address)), + read_callback_(std::move(read_callback)), + weak_factory_(this) { + DCHECK(adapter_); + adapter_->AddObserver(this); + DCHECK(!address_.empty()); +} + +FidoBleConnection::~FidoBleConnection() { + adapter_->RemoveObserver(this); +} + +BluetoothDevice* FidoBleConnection::GetBleDevice() { + return adapter_->GetDevice(address()); +} + +const BluetoothDevice* FidoBleConnection::GetBleDevice() const { + return adapter_->GetDevice(address()); +} + +FidoBleConnection::FidoBleConnection(BluetoothAdapter* adapter, + std::string device_address) + : adapter_(adapter), + address_(std::move(device_address)), + weak_factory_(this) { + adapter_->AddObserver(this); +} + +void FidoBleConnection::Connect(ConnectionCallback callback) { + auto* device = GetBleDevice(); + if (!device) { + LOG(ERROR) << "Failed to get Device."; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(callback), false)); + return; + } + + pending_connection_callback_ = std::move(callback); + device->CreateGattConnection( + base::Bind(&FidoBleConnection::OnCreateGattConnection, + weak_factory_.GetWeakPtr()), + base::Bind(&FidoBleConnection::OnCreateGattConnectionError, + weak_factory_.GetWeakPtr())); +} + +void FidoBleConnection::ReadControlPointLength( + ControlPointLengthCallback callback) { + const auto* fido_service = GetFidoService(GetBleDevice()); + if (!fido_service) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(callback), base::nullopt)); + return; + } + + if (!control_point_length_id_) { + LOG(ERROR) << "Failed to get Control Point Length."; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(callback), base::nullopt)); + return; + } + + BluetoothRemoteGattCharacteristic* control_point_length = + fido_service->GetCharacteristic(*control_point_length_id_); + if (!control_point_length) { + LOG(ERROR) << "No Control Point Length characteristic present."; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(callback), base::nullopt)); + return; + } + + // Work around legacy APIs. Only one of the callbacks to + // ReadRemoteCharacteristic() gets invoked, but we don't know which one. + auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback)); + control_point_length->ReadRemoteCharacteristic( + base::Bind(OnReadControlPointLength, copyable_callback), + base::Bind(OnReadControlPointLengthError, copyable_callback)); +} + +void FidoBleConnection::WriteControlPoint(const std::vector<uint8_t>& data, + WriteCallback callback) { + const auto* fido_service = GetFidoService(GetBleDevice()); + if (!fido_service) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(callback), false)); + return; + } + + if (!control_point_id_) { + LOG(ERROR) << "Failed to get Control Point."; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(callback), false)); + return; + } + + BluetoothRemoteGattCharacteristic* control_point = + fido_service->GetCharacteristic(*control_point_id_); + if (!control_point) { + LOG(ERROR) << "Control Point characteristic not present."; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(callback), false)); + return; + } + +#if defined(OS_MACOSX) + // Attempt a write without response for performance reasons. Fall back to a + // confirmed write in case of failure, e.g. when the characteristic does not + // provide the required property. + if (control_point->WriteWithoutResponse(data)) { + VLOG(2) << "Write without response succeeded."; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(callback), true)); + return; + } +#endif // defined(OS_MACOSX) + + auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback)); + control_point->WriteRemoteCharacteristic( + data, base::Bind(OnWriteRemoteCharacteristic, copyable_callback), + base::Bind(OnWriteRemoteCharacteristicError, copyable_callback)); +} + +void FidoBleConnection::OnCreateGattConnection( + std::unique_ptr<BluetoothGattConnection> connection) { + DCHECK(pending_connection_callback_); + connection_ = std::move(connection); + + BluetoothDevice* device = adapter_->GetDevice(address_); + if (!device) { + LOG(ERROR) << "Failed to get Device."; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(std::move(pending_connection_callback_), false)); + return; + } + + if (device->IsGattServicesDiscoveryComplete()) + ConnectToFidoService(); + else + waiting_for_gatt_discovery_ = true; +} + +void FidoBleConnection::OnCreateGattConnectionError( + BluetoothDevice::ConnectErrorCode error_code) { + DCHECK(pending_connection_callback_); + LOG(ERROR) << "CreateGattConnection() failed: " << ToString(error_code); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(std::move(pending_connection_callback_), false)); +} + +void FidoBleConnection::ConnectToFidoService() { + DCHECK(pending_connection_callback_); + const auto* fido_service = GetFidoService(GetBleDevice()); + if (!fido_service) { + LOG(ERROR) << "Failed to get Fido Service."; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(std::move(pending_connection_callback_), false)); + return; + } + + for (const auto* characteristic : fido_service->GetCharacteristics()) { + std::string uuid = characteristic->GetUUID().canonical_value(); + if (uuid == kFidoControlPointLengthUUID) { + control_point_length_id_ = characteristic->GetIdentifier(); + VLOG(2) << "Got Fido Control Point Length: " << *control_point_length_id_; + continue; + } + + if (uuid == kFidoControlPointUUID) { + control_point_id_ = characteristic->GetIdentifier(); + VLOG(2) << "Got Fido Control Point: " << *control_point_id_; + continue; + } + + if (uuid == kFidoStatusUUID) { + status_id_ = characteristic->GetIdentifier(); + VLOG(2) << "Got Fido Status: " << *status_id_; + continue; + } + + if (uuid == kFidoServiceRevisionUUID) { + service_revision_id_ = characteristic->GetIdentifier(); + VLOG(2) << "Got Fido Service Revision: " << *service_revision_id_; + continue; + } + + if (uuid == kFidoServiceRevisionBitfieldUUID) { + service_revision_bitfield_id_ = characteristic->GetIdentifier(); + VLOG(2) << "Got Fido Service Revision Bitfield: " + << *service_revision_bitfield_id_; + } + } + + if (!control_point_length_id_ || !control_point_id_ || !status_id_ || + (!service_revision_id_ && !service_revision_bitfield_id_)) { + LOG(ERROR) << "Fido Characteristics missing."; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(std::move(pending_connection_callback_), false)); + return; + } + + // In case the bitfield characteristic is present, the client has to select a + // supported bersion by writing the corresponding bit. Reference: + // https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#ble-protocol-overview + if (service_revision_bitfield_id_) { + auto callback = base::Bind(&FidoBleConnection::OnReadServiceRevisions, + weak_factory_.GetWeakPtr()); + fido_service->GetCharacteristic(*service_revision_bitfield_id_) + ->ReadRemoteCharacteristic( + base::Bind(OnReadServiceRevisionBitfield, callback), + base::Bind(OnReadServiceRevisionBitfieldError, callback)); + return; + } + + StartNotifySession(); +} + +void FidoBleConnection::OnReadServiceRevisions( + std::vector<ServiceRevision> service_revisions) { + DCHECK(pending_connection_callback_); + if (service_revisions.empty()) { + LOG(ERROR) << "Could not obtain Service Revisions."; + std::move(pending_connection_callback_).Run(false); + return; + } + + // Write the most recent supported service revision back to the + // characteristic. Note that this information is currently not used in another + // way, as we will still attempt a CTAP GetInfo() command, even if only U2F is + // supported. + // TODO(https://crbug.com/780078): Consider short circuiting to the + // U2F logic if FIDO2 is not supported. + DCHECK_EQ( + *std::min_element(service_revisions.begin(), service_revisions.end()), + service_revisions.back()); + WriteServiceRevision(service_revisions.back()); +} + +void FidoBleConnection::WriteServiceRevision(ServiceRevision service_revision) { + auto callback = base::BindOnce(&FidoBleConnection::OnServiceRevisionWritten, + weak_factory_.GetWeakPtr()); + + const auto* fido_service = GetFidoService(GetBleDevice()); + if (!fido_service) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(callback), false)); + return; + } + + auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback)); + DCHECK(service_revision_bitfield_id_); + fido_service->GetCharacteristic(*service_revision_bitfield_id_) + ->WriteRemoteCharacteristic( + {static_cast<uint8_t>(service_revision)}, + base::Bind(OnWriteRemoteCharacteristic, copyable_callback), + base::Bind(OnWriteRemoteCharacteristicError, copyable_callback)); +} + +void FidoBleConnection::OnServiceRevisionWritten(bool success) { + DCHECK(pending_connection_callback_); + if (success) { + StartNotifySession(); + return; + } + + std::move(pending_connection_callback_).Run(false); +} + +void FidoBleConnection::StartNotifySession() { + DCHECK(pending_connection_callback_); + const auto* fido_service = GetFidoService(GetBleDevice()); + if (!fido_service) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(std::move(pending_connection_callback_), false)); + return; + } + + DCHECK(status_id_); + fido_service->GetCharacteristic(*status_id_) + ->StartNotifySession( + base::Bind(&FidoBleConnection::OnStartNotifySession, + weak_factory_.GetWeakPtr()), + base::Bind(&FidoBleConnection::OnStartNotifySessionError, + weak_factory_.GetWeakPtr())); +} + +void FidoBleConnection::OnStartNotifySession( + std::unique_ptr<BluetoothGattNotifySession> notify_session) { + notify_session_ = std::move(notify_session); + VLOG(2) << "Created notification session. Connection established."; + std::move(pending_connection_callback_).Run(true); +} + +void FidoBleConnection::OnStartNotifySessionError( + BluetoothGattService::GattErrorCode error_code) { + LOG(ERROR) << "StartNotifySession() failed: " << ToString(error_code); + std::move(pending_connection_callback_).Run(false); +} + +void FidoBleConnection::GattCharacteristicValueChanged( + BluetoothAdapter* adapter, + BluetoothRemoteGattCharacteristic* characteristic, + const std::vector<uint8_t>& value) { + if (characteristic->GetIdentifier() != status_id_) + return; + VLOG(2) << "Status characteristic value changed."; + read_callback_.Run(value); +} + +void FidoBleConnection::GattServicesDiscovered(BluetoothAdapter* adapter, + BluetoothDevice* device) { + if (adapter != adapter_ || device->GetAddress() != address_) + return; + + if (waiting_for_gatt_discovery_) { + waiting_for_gatt_discovery_ = false; + ConnectToFidoService(); + } +} + +// static +void FidoBleConnection::OnReadControlPointLength( + ControlPointLengthCallback callback, + const std::vector<uint8_t>& value) { + if (value.size() != 2) { + LOG(ERROR) << "Wrong Control Point Length: " << value.size() << " bytes"; + std::move(callback).Run(base::nullopt); + return; + } + + uint16_t length = (value[0] << 8) | value[1]; + VLOG(2) << "Control Point Length: " << length; + std::move(callback).Run(length); +} + +// static +void FidoBleConnection::OnReadControlPointLengthError( + ControlPointLengthCallback callback, + BluetoothGattService::GattErrorCode error_code) { + LOG(ERROR) << "Error reading Control Point Length: " << ToString(error_code); + std::move(callback).Run(base::nullopt); +} + +} // namespace device diff --git a/chromium/device/fido/fido_ble_connection.h b/chromium/device/fido/ble/fido_ble_connection.h index f7efb7044af..34174629662 100644 --- a/chromium/device/fido/fido_ble_connection.h +++ b/chromium/device/fido/ble/fido_ble_connection.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef DEVICE_FIDO_FIDO_BLE_CONNECTION_H_ -#define DEVICE_FIDO_FIDO_BLE_CONNECTION_H_ +#ifndef DEVICE_FIDO_BLE_FIDO_BLE_CONNECTION_H_ +#define DEVICE_FIDO_BLE_FIDO_BLE_CONNECTION_H_ #include <stdint.h> @@ -27,72 +27,56 @@ namespace device { class BluetoothGattConnection; class BluetoothGattNotifySession; class BluetoothRemoteGattCharacteristic; -class BluetoothRemoteGattService; -// A connection to the U2F service of an authenticator over BLE. Detailed +// A connection to the Fido service of an authenticator over BLE. Detailed // specification of the BLE device can be found here: -// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-bt-protocol-v1.2-ps-20170411.html#h2_gatt-service-description -// -// Currently this code does not handle devices that need pairing. This is fine -// for non-BlueZ platforms, as here accessing a protected characteristic will -// trigger an OS level dialog if pairing is required. However, for BlueZ -// platforms pairing must have been done externally, for example using the -// `bluetoothctl` command. -// -// TODO(crbug.com/763303): Add support for pairing from within this class and -// provide users with an option to manually specify a PIN code. +// https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#ble class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleConnection : public BluetoothAdapter::Observer { public: - enum class ServiceRevision { - VERSION_1_0, - VERSION_1_1, - VERSION_1_2, + // Valid Service Revisions. Reference: + // https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#ble-fido-service + enum class ServiceRevision : uint8_t { + kU2f11 = 1 << 7, + kU2f12 = 1 << 6, + kFido2 = 1 << 5, }; // This callback informs clients repeatedly about changes in the device // connection. This class makes an initial connection attempt on construction, // which result in returned via this callback. Future invocations happen if // devices connect or disconnect from the adapter. - using ConnectionStatusCallback = base::RepeatingCallback<void(bool)>; + using ConnectionCallback = base::OnceCallback<void(bool)>; using WriteCallback = base::OnceCallback<void(bool)>; using ReadCallback = base::RepeatingCallback<void(std::vector<uint8_t>)>; using ControlPointLengthCallback = base::OnceCallback<void(base::Optional<uint16_t>)>; - using ServiceRevisionsCallback = - base::OnceCallback<void(std::set<ServiceRevision>)>; - FidoBleConnection(std::string device_address, - ConnectionStatusCallback connection_status_callback, + FidoBleConnection(BluetoothAdapter* adapter, + std::string device_address, ReadCallback read_callback); ~FidoBleConnection() override; const std::string& address() const { return address_; } + + BluetoothDevice* GetBleDevice(); const BluetoothDevice* GetBleDevice() const; - virtual void Connect(); + virtual void Connect(ConnectionCallback callback); virtual void ReadControlPointLength(ControlPointLengthCallback callback); - virtual void ReadServiceRevisions(ServiceRevisionsCallback callback); virtual void WriteControlPoint(const std::vector<uint8_t>& data, WriteCallback callback); - virtual void WriteServiceRevision(ServiceRevision service_revision, - WriteCallback callback); protected: - explicit FidoBleConnection(std::string device_address); + // Used for testing. + FidoBleConnection(BluetoothAdapter* adapter, std::string device_address); + scoped_refptr<BluetoothAdapter> adapter_; std::string address_; - ConnectionStatusCallback connection_status_callback_; ReadCallback read_callback_; private: // BluetoothAdapter::Observer: - void DeviceAdded(BluetoothAdapter* adapter, BluetoothDevice* device) override; - void DeviceAddressChanged(BluetoothAdapter* adapter, - BluetoothDevice* device, - const std::string& old_address) override; - void DeviceChanged(BluetoothAdapter* adapter, - BluetoothDevice* device) override; void GattCharacteristicValueChanged( BluetoothAdapter* adapter, BluetoothRemoteGattCharacteristic* characteristic, @@ -100,61 +84,41 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleConnection void GattServicesDiscovered(BluetoothAdapter* adapter, BluetoothDevice* device) override; - void OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter); - - void CreateGattConnection(); void OnCreateGattConnection( std::unique_ptr<BluetoothGattConnection> connection); void OnCreateGattConnectionError( BluetoothDevice::ConnectErrorCode error_code); - void ConnectToU2fService(); + void ConnectToFidoService(); + void OnReadServiceRevisions(std::vector<ServiceRevision> service_revisions); + + void WriteServiceRevision(ServiceRevision service_revision); + void OnServiceRevisionWritten(bool success); + void StartNotifySession(); void OnStartNotifySession( std::unique_ptr<BluetoothGattNotifySession> notify_session); void OnStartNotifySessionError( BluetoothGattService::GattErrorCode error_code); - void OnConnectionError(); - - const BluetoothRemoteGattService* GetFidoService() const; - static void OnReadControlPointLength(ControlPointLengthCallback callback, const std::vector<uint8_t>& value); static void OnReadControlPointLengthError( ControlPointLengthCallback callback, BluetoothGattService::GattErrorCode error_code); - void OnReadServiceRevision(base::OnceClosure callback, - const std::vector<uint8_t>& value); - void OnReadServiceRevisionError( - base::OnceClosure callback, - BluetoothGattService::GattErrorCode error_code); - - void OnReadServiceRevisionBitfield(base::OnceClosure callback, - const std::vector<uint8_t>& value); - void OnReadServiceRevisionBitfieldError( - base::OnceClosure callback, - BluetoothGattService::GattErrorCode error_code); - void ReturnServiceRevisions(ServiceRevisionsCallback callback); - - static void OnWrite(WriteCallback callback); - static void OnWriteError(WriteCallback callback, - BluetoothGattService::GattErrorCode error_code); - - scoped_refptr<BluetoothAdapter> adapter_; std::unique_ptr<BluetoothGattConnection> connection_; std::unique_ptr<BluetoothGattNotifySession> notify_session_; - base::Optional<std::string> u2f_service_id_; + ConnectionCallback pending_connection_callback_; + bool waiting_for_gatt_discovery_ = false; + base::Optional<std::string> control_point_length_id_; base::Optional<std::string> control_point_id_; base::Optional<std::string> status_id_; base::Optional<std::string> service_revision_id_; base::Optional<std::string> service_revision_bitfield_id_; - std::set<ServiceRevision> service_revisions_; - base::WeakPtrFactory<FidoBleConnection> weak_factory_; DISALLOW_COPY_AND_ASSIGN(FidoBleConnection); @@ -162,4 +126,4 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleConnection } // namespace device -#endif // DEVICE_FIDO_FIDO_BLE_CONNECTION_H_ +#endif // DEVICE_FIDO_BLE_FIDO_BLE_CONNECTION_H_ diff --git a/chromium/device/fido/ble/fido_ble_connection_unittest.cc b/chromium/device/fido/ble/fido_ble_connection_unittest.cc new file mode 100644 index 00000000000..865a92fadf8 --- /dev/null +++ b/chromium/device/fido/ble/fido_ble_connection_unittest.cc @@ -0,0 +1,738 @@ +// 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 "device/fido/ble/fido_ble_connection.h" + +#include <bitset> +#include <utility> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/memory/ptr_util.h" +#include "base/run_loop.h" +#include "base/strings/string_piece.h" +#include "base/test/scoped_task_environment.h" +#include "base/threading/thread_task_runner_handle.h" +#include "build/build_config.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" +#include "device/bluetooth/test/bluetooth_test.h" +#include "device/bluetooth/test/mock_bluetooth_adapter.h" +#include "device/bluetooth/test/mock_bluetooth_device.h" +#include "device/bluetooth/test/mock_bluetooth_gatt_characteristic.h" +#include "device/bluetooth/test/mock_bluetooth_gatt_connection.h" +#include "device/bluetooth/test/mock_bluetooth_gatt_notify_session.h" +#include "device/bluetooth/test/mock_bluetooth_gatt_service.h" +#include "device/fido/ble/fido_ble_uuids.h" +#include "device/fido/test_callback_receiver.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_ANDROID) +#include "device/bluetooth/test/bluetooth_test_android.h" +#elif defined(OS_MACOSX) +#include "device/bluetooth/test/bluetooth_test_mac.h" +#elif defined(OS_WIN) +#include "device/bluetooth/test/bluetooth_test_win.h" +#elif defined(OS_CHROMEOS) || defined(OS_LINUX) +#include "device/bluetooth/test/bluetooth_test_bluez.h" +#endif + +namespace device { + +using ::testing::_; +using ::testing::ElementsAre; +using ::testing::Invoke; +using ::testing::IsEmpty; +using ::testing::Return; + +using NiceMockBluetoothAdapter = ::testing::NiceMock<MockBluetoothAdapter>; +using NiceMockBluetoothDevice = ::testing::NiceMock<MockBluetoothDevice>; +using NiceMockBluetoothGattService = + ::testing::NiceMock<MockBluetoothGattService>; +using NiceMockBluetoothGattCharacteristic = + ::testing::NiceMock<MockBluetoothGattCharacteristic>; +using NiceMockBluetoothGattConnection = + ::testing::NiceMock<MockBluetoothGattConnection>; +using NiceMockBluetoothGattNotifySession = + ::testing::NiceMock<MockBluetoothGattNotifySession>; + +namespace { + +constexpr auto kDefaultServiceRevision = + static_cast<uint8_t>(FidoBleConnection::ServiceRevision::kFido2); + +std::vector<uint8_t> ToByteVector(base::StringPiece str) { + return std::vector<uint8_t>(str.begin(), str.end()); +} + +BluetoothDevice* GetMockDevice(MockBluetoothAdapter* adapter, + const std::string& address) { + const std::vector<BluetoothDevice*> devices = adapter->GetMockDevices(); + auto found = std::find_if(devices.begin(), devices.end(), + [&address](const auto* device) { + return device->GetAddress() == address; + }); + return found != devices.end() ? *found : nullptr; +} + +class TestReadCallback { + public: + void OnRead(std::vector<uint8_t> value) { + value_ = std::move(value); + run_loop_->Quit(); + } + + const std::vector<uint8_t> WaitForResult() { + run_loop_->Run(); + run_loop_.emplace(); + return value_; + } + + FidoBleConnection::ReadCallback GetCallback() { + return base::BindRepeating(&TestReadCallback::OnRead, + base::Unretained(this)); + } + + private: + std::vector<uint8_t> value_; + base::Optional<base::RunLoop> run_loop_{base::in_place}; +}; + +using TestConnectionCallbackReceiver = test::ValueCallbackReceiver<bool>; + +using TestReadControlPointLengthCallback = + test::ValueCallbackReceiver<base::Optional<uint16_t>>; + +using TestReadServiceRevisionsCallback = + test::ValueCallbackReceiver<std::set<FidoBleConnection::ServiceRevision>>; + +using TestWriteCallback = test::ValueCallbackReceiver<bool>; +} // namespace + +class FidoBleConnectionTest : public ::testing::Test { + public: + FidoBleConnectionTest() { + ON_CALL(*adapter_, GetDevice(_)) + .WillByDefault(Invoke([this](const std::string& address) { + return GetMockDevice(adapter_.get(), address); + })); + + BluetoothAdapterFactory::SetAdapterForTesting(adapter_); + } + + BluetoothAdapter* adapter() { return adapter_.get(); } + MockBluetoothDevice* device() { return fido_device_; } + + void AddFidoDevice(const std::string& device_address) { + auto fido_device = std::make_unique<NiceMockBluetoothDevice>( + adapter_.get(), /* bluetooth_class */ 0u, + BluetoothTest::kTestDeviceNameU2f, device_address, /* paired */ true, + /* connected */ false); + fido_device_ = fido_device.get(); + adapter_->AddMockDevice(std::move(fido_device)); + + ON_CALL(*fido_device_, GetGattServices()) + .WillByDefault( + Invoke(fido_device_, &MockBluetoothDevice::GetMockServices)); + + ON_CALL(*fido_device_, GetGattService(_)) + .WillByDefault( + Invoke(fido_device_, &MockBluetoothDevice::GetMockService)); + AddFidoService(); + } + + void SetupConnectingFidoDevice(const std::string& device_address) { + ON_CALL(*fido_device_, CreateGattConnection) + .WillByDefault( + Invoke([this, &device_address](const auto& callback, auto&&) { + connection_ = + new NiceMockBluetoothGattConnection(adapter_, device_address); + callback.Run(std::move(base::WrapUnique(connection_))); + })); + + ON_CALL(*fido_device_, IsGattServicesDiscoveryComplete) + .WillByDefault(Return(true)); + + ON_CALL(*fido_service_revision_bitfield_, ReadRemoteCharacteristic) + .WillByDefault(Invoke([=](auto&& callback, auto&&) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(callback, + std::vector<uint8_t>({kDefaultServiceRevision}))); + })); + + ON_CALL(*fido_service_revision_bitfield_, WriteRemoteCharacteristic) + .WillByDefault(Invoke([=](auto&&, auto&& callback, auto&&) { + base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, callback); + })); + + ON_CALL(*fido_status_, StartNotifySession(_, _)) + .WillByDefault(Invoke([this](const auto& callback, auto&&) { + notify_session_ = new NiceMockBluetoothGattNotifySession( + fido_status_->GetWeakPtr()); + callback.Run(base::WrapUnique(notify_session_)); + })); + } + + void SimulateGattDiscoveryComplete(bool complete) { + EXPECT_CALL(*fido_device_, IsGattServicesDiscoveryComplete) + .WillOnce(Return(complete)); + } + + void SimulateGattConnectionError() { + EXPECT_CALL(*fido_device_, CreateGattConnection) + .WillOnce(Invoke([](auto&&, auto&& error_callback) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, BluetoothDevice::ERROR_FAILED)); + })); + } + + void SimulateGattNotifySessionStartError() { + EXPECT_CALL(*fido_status_, StartNotifySession(_, _)) + .WillOnce(Invoke([](auto&&, auto&& error_callback) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(error_callback, + BluetoothGattService::GATT_ERROR_FAILED)); + })); + } + + void NotifyStatusChanged(const std::vector<uint8_t>& value) { + for (auto& observer : adapter_->GetObservers()) + observer.GattCharacteristicValueChanged(adapter_.get(), fido_status_, + value); + } + + void NotifyGattServicesDiscovered() { + adapter_->NotifyGattServicesDiscovered(fido_device_); + } + + void SetNextReadControlPointLengthReponse(bool success, + const std::vector<uint8_t>& value) { + EXPECT_CALL(*fido_control_point_length_, ReadRemoteCharacteristic(_, _)) + .WillOnce(Invoke([success, value](const auto& callback, + const auto& error_callback) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + success + ? base::BindOnce(callback, value) + : base::BindOnce(error_callback, + BluetoothGattService::GATT_ERROR_FAILED)); + })); + } + + void SetNextReadServiceRevisionResponse(bool success, + const std::vector<uint8_t>& value) { + EXPECT_CALL(*fido_service_revision_, ReadRemoteCharacteristic(_, _)) + .WillOnce(Invoke([success, value](const auto& callback, + const auto& error_callback) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + success + ? base::BindOnce(callback, value) + : base::BindOnce(error_callback, + BluetoothGattService::GATT_ERROR_FAILED)); + })); + } + + void SetNextReadServiceRevisionBitfieldResponse( + bool success, + const std::vector<uint8_t>& value) { + EXPECT_CALL(*fido_service_revision_bitfield_, + ReadRemoteCharacteristic(_, _)) + .WillOnce(Invoke([success, value](const auto& callback, + const auto& error_callback) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + success + ? base::BindOnce(callback, value) + : base::BindOnce(error_callback, + BluetoothGattService::GATT_ERROR_FAILED)); + })); + } + + void SetNextWriteControlPointResponse(bool success) { +// For performance reasons we try writes without responses first on macOS. +#if defined(OS_MACOSX) + EXPECT_CALL(*fido_control_point_, WriteWithoutResponse) + .WillOnce(Return(success)); + if (success) + return; +#else + EXPECT_CALL(*fido_control_point_, WriteWithoutResponse).Times(0); +#endif // defined(OS_MACOSX) + + EXPECT_CALL(*fido_control_point_, WriteRemoteCharacteristic(_, _, _)) + .WillOnce(Invoke([success](const auto& data, const auto& callback, + const auto& error_callback) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + success + ? base::BindOnce(callback) + : base::BindOnce(error_callback, + BluetoothGattService::GATT_ERROR_FAILED)); + })); + } + + void SetNextWriteServiceRevisionResponse(std::vector<uint8_t> expected_data, + bool success) { + EXPECT_CALL(*fido_service_revision_bitfield_, + WriteRemoteCharacteristic(expected_data, _, _)) + .WillOnce(Invoke([success](const auto& data, const auto& callback, + const auto& error_callback) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + success + ? base::BindOnce(callback) + : base::BindOnce(error_callback, + BluetoothGattService::GATT_ERROR_FAILED)); + })); + } + + void AddFidoService() { + auto fido_service = std::make_unique<NiceMockBluetoothGattService>( + fido_device_, "fido_service", BluetoothUUID(kFidoServiceUUID), + /* is_primary */ true, /* is_local */ false); + fido_service_ = fido_service.get(); + fido_device_->AddMockService(std::move(fido_service)); + + static constexpr bool kIsLocal = false; + { + auto fido_control_point = + std::make_unique<NiceMockBluetoothGattCharacteristic>( + fido_service_, "fido_control_point", + BluetoothUUID(kFidoControlPointUUID), kIsLocal, + BluetoothGattCharacteristic::PROPERTY_WRITE, + BluetoothGattCharacteristic::PERMISSION_NONE); + fido_control_point_ = fido_control_point.get(); + fido_service_->AddMockCharacteristic(std::move(fido_control_point)); + } + + { + auto fido_status = std::make_unique<NiceMockBluetoothGattCharacteristic>( + fido_service_, "fido_status", BluetoothUUID(kFidoStatusUUID), + kIsLocal, BluetoothGattCharacteristic::PROPERTY_NOTIFY, + BluetoothGattCharacteristic::PERMISSION_NONE); + fido_status_ = fido_status.get(); + fido_service_->AddMockCharacteristic(std::move(fido_status)); + } + + { + auto fido_control_point_length = + std::make_unique<NiceMockBluetoothGattCharacteristic>( + fido_service_, "fido_control_point_length", + BluetoothUUID(kFidoControlPointLengthUUID), kIsLocal, + BluetoothGattCharacteristic::PROPERTY_READ, + BluetoothGattCharacteristic::PERMISSION_NONE); + fido_control_point_length_ = fido_control_point_length.get(); + fido_service_->AddMockCharacteristic( + std::move(fido_control_point_length)); + } + + { + auto fido_service_revision = + std::make_unique<NiceMockBluetoothGattCharacteristic>( + fido_service_, "fido_service_revision", + BluetoothUUID(kFidoServiceRevisionUUID), kIsLocal, + BluetoothGattCharacteristic::PROPERTY_READ, + BluetoothGattCharacteristic::PERMISSION_NONE); + fido_service_revision_ = fido_service_revision.get(); + fido_service_->AddMockCharacteristic(std::move(fido_service_revision)); + } + + { + auto fido_service_revision_bitfield = + std::make_unique<NiceMockBluetoothGattCharacteristic>( + fido_service_, "fido_service_revision_bitfield", + BluetoothUUID(kFidoServiceRevisionBitfieldUUID), kIsLocal, + BluetoothGattCharacteristic::PROPERTY_READ | + BluetoothGattCharacteristic::PROPERTY_WRITE, + BluetoothGattCharacteristic::PERMISSION_NONE); + fido_service_revision_bitfield_ = fido_service_revision_bitfield.get(); + fido_service_->AddMockCharacteristic( + std::move(fido_service_revision_bitfield)); + } + } + + private: + base::test::ScopedTaskEnvironment scoped_task_environment_; + + scoped_refptr<MockBluetoothAdapter> adapter_ = + base::MakeRefCounted<NiceMockBluetoothAdapter>(); + + MockBluetoothDevice* fido_device_; + MockBluetoothGattService* fido_service_; + + MockBluetoothGattCharacteristic* fido_control_point_; + MockBluetoothGattCharacteristic* fido_status_; + MockBluetoothGattCharacteristic* fido_control_point_length_; + MockBluetoothGattCharacteristic* fido_service_revision_; + MockBluetoothGattCharacteristic* fido_service_revision_bitfield_; + + MockBluetoothGattConnection* connection_; + MockBluetoothGattNotifySession* notify_session_; +}; + +TEST_F(FidoBleConnectionTest, Address) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + FidoBleConnection connection(adapter(), device_address, base::DoNothing()); + connection.Connect(base::DoNothing()); + EXPECT_EQ(device_address, connection.address()); +} + +TEST_F(FidoBleConnectionTest, DeviceNotPresent) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + FidoBleConnection connection(adapter(), device_address, + base::DoNothing()); + + TestConnectionCallbackReceiver connection_callback_receiver; + connection.Connect(connection_callback_receiver.callback()); + connection_callback_receiver.WaitForCallback(); + EXPECT_FALSE(connection_callback_receiver.value()); +} + +TEST_F(FidoBleConnectionTest, PreConnected) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + AddFidoDevice(device_address); + SetupConnectingFidoDevice(device_address); + FidoBleConnection connection(adapter(), device_address, base::DoNothing()); + + TestConnectionCallbackReceiver connection_callback_receiver; + connection.Connect(connection_callback_receiver.callback()); + connection_callback_receiver.WaitForCallback(); + EXPECT_TRUE(connection_callback_receiver.value()); +} + +TEST_F(FidoBleConnectionTest, NoConnectionWithoutCompletedGattDiscovery) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + AddFidoDevice(device_address); + SetupConnectingFidoDevice(device_address); + FidoBleConnection connection(adapter(), device_address, base::DoNothing()); + + SimulateGattDiscoveryComplete(false); + TestConnectionCallbackReceiver connection_callback_receiver; + connection.Connect(connection_callback_receiver.callback()); + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(connection_callback_receiver.was_called()); + + NotifyGattServicesDiscovered(); + connection_callback_receiver.WaitForCallback(); + EXPECT_TRUE(connection_callback_receiver.value()); +} + +TEST_F(FidoBleConnectionTest, GattServicesDiscoveredIgnoredBeforeConnection) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + AddFidoDevice(device_address); + SetupConnectingFidoDevice(device_address); + FidoBleConnection connection(adapter(), device_address, + base::DoNothing()); + NotifyGattServicesDiscovered(); + + SimulateGattDiscoveryComplete(false); + TestConnectionCallbackReceiver connection_callback_receiver; + connection.Connect(connection_callback_receiver.callback()); + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(connection_callback_receiver.was_called()); + + NotifyGattServicesDiscovered(); + connection_callback_receiver.WaitForCallback(); + EXPECT_TRUE(connection_callback_receiver.value()); +} + +TEST_F(FidoBleConnectionTest, GattServicesDiscoveredAgain) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + AddFidoDevice(device_address); + SetupConnectingFidoDevice(device_address); + FidoBleConnection connection(adapter(), device_address, + base::DoNothing()); + + TestConnectionCallbackReceiver connection_callback_receiver; + connection.Connect(connection_callback_receiver.callback()); + NotifyGattServicesDiscovered(); + connection_callback_receiver.WaitForCallback(); + EXPECT_TRUE(connection_callback_receiver.value()); + + // A second call to the event handler should not trigger another attempt to + // obtain Gatt Services. + EXPECT_CALL(*device(), GetGattServices).Times(0); + EXPECT_CALL(*device(), GetGattService).Times(0); + NotifyGattServicesDiscovered(); +} + +TEST_F(FidoBleConnectionTest, SimulateGattConnectionError) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + AddFidoDevice(device_address); + SetupConnectingFidoDevice(device_address); + FidoBleConnection connection(adapter(), device_address, base::DoNothing()); + + SimulateGattConnectionError(); + TestConnectionCallbackReceiver connection_callback_receiver; + connection.Connect(connection_callback_receiver.callback()); + connection_callback_receiver.WaitForCallback(); + EXPECT_FALSE(connection_callback_receiver.value()); +} + +TEST_F(FidoBleConnectionTest, SimulateGattNotifySessionStartError) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + AddFidoDevice(device_address); + SetupConnectingFidoDevice(device_address); + FidoBleConnection connection(adapter(), device_address, + base::DoNothing()); + + SimulateGattNotifySessionStartError(); + TestConnectionCallbackReceiver connection_callback_receiver; + connection.Connect(connection_callback_receiver.callback()); + connection_callback_receiver.WaitForCallback(); + EXPECT_FALSE(connection_callback_receiver.value()); +} + +TEST_F(FidoBleConnectionTest, MultipleServiceRevisions) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + AddFidoDevice(device_address); + SetupConnectingFidoDevice(device_address); + + static constexpr struct { + std::bitset<8> supported_revisions; + std::bitset<8> selected_revision; + } test_cases[] = { + // Only U2F 1.1 is supported, pick it. + {0b1000'0000, 0b1000'0000}, + // Only U2F 1.2 is supported, pick it. + {0b0100'0000, 0b0100'0000}, + // U2F 1.1 and U2F 1.2 are supported, pick U2F 1.2. + {0b1100'0000, 0b0100'0000}, + // Only FIDO2 is supported, pick it. + {0b0010'0000, 0b0010'0000}, + // U2F 1.1 and FIDO2 are supported, pick FIDO2. + {0b1010'0000, 0b0010'0000}, + // U2F 1.2 and FIDO2 are supported, pick FIDO2. + {0b0110'0000, 0b0010'0000}, + // U2F 1.1, U2F 1.2 and FIDO2 are supported, pick FIDO2. + {0b1110'0000, 0b0010'0000}, + // U2F 1.1 and a future revision are supported, pick U2F 1.1. + {0b1000'1000, 0b1000'0000}, + // U2F 1.2 and a future revision are supported, pick U2F 1.2. + {0b0100'1000, 0b0100'0000}, + // FIDO2 and a future revision are supported, pick FIDO2. + {0b0010'1000, 0b0010'0000}, + }; + + for (const auto& test_case : test_cases) { + SCOPED_TRACE(::testing::Message() + << "Supported Revisions: " << test_case.supported_revisions + << ", Selected Revision: " << test_case.selected_revision); + SetNextReadServiceRevisionBitfieldResponse( + true, {test_case.supported_revisions.to_ulong()}); + + SetNextWriteServiceRevisionResponse( + {test_case.selected_revision.to_ulong()}, true); + + FidoBleConnection connection(adapter(), device_address, + base::DoNothing()); + TestConnectionCallbackReceiver connection_callback_receiver; + connection.Connect(connection_callback_receiver.callback()); + connection_callback_receiver.WaitForCallback(); + EXPECT_TRUE(connection_callback_receiver.value()); + } +} + +TEST_F(FidoBleConnectionTest, UnsupportedServiceRevisions) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + AddFidoDevice(device_address); + SetupConnectingFidoDevice(device_address); + + // Test failure cases. + static constexpr struct { + std::bitset<8> supported_revisions; + } test_cases[] = { + {0b0000'0000}, // No Service Revision. + {0b0001'0000}, // Unsupported Service Revision (4th bit). + {0b0000'1000}, // Unsupported Service Revision (3th bit). + {0b0000'0100}, // Unsupported Service Revision (2th bit). + {0b0000'0010}, // Unsupported Service Revision (1th bit). + {0b0000'0001}, // Unsupported Service Revision (0th bit). + }; + + for (const auto& test_case : test_cases) { + SCOPED_TRACE(::testing::Message() + << "Supported Revisions: " << test_case.supported_revisions); + SetNextReadServiceRevisionBitfieldResponse( + true, {test_case.supported_revisions.to_ulong()}); + + FidoBleConnection connection(adapter(), device_address, + base::DoNothing()); + TestConnectionCallbackReceiver connection_callback_receiver; + connection.Connect(connection_callback_receiver.callback()); + connection_callback_receiver.WaitForCallback(); + EXPECT_FALSE(connection_callback_receiver.value()); + } +} + +TEST_F(FidoBleConnectionTest, ReadServiceRevisionsFails) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + + AddFidoDevice(device_address); + SetupConnectingFidoDevice(device_address); + SetNextReadServiceRevisionBitfieldResponse(false, {}); + + FidoBleConnection connection(adapter(), device_address, + base::DoNothing()); + TestConnectionCallbackReceiver connection_callback_receiver; + connection.Connect(connection_callback_receiver.callback()); + connection_callback_receiver.WaitForCallback(); + EXPECT_FALSE(connection_callback_receiver.value()); +} + +TEST_F(FidoBleConnectionTest, WriteServiceRevisionsFails) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + + AddFidoDevice(device_address); + SetupConnectingFidoDevice(device_address); + SetNextReadServiceRevisionBitfieldResponse(true, {kDefaultServiceRevision}); + SetNextWriteServiceRevisionResponse({kDefaultServiceRevision}, false); + + FidoBleConnection connection(adapter(), device_address, + base::DoNothing()); + TestConnectionCallbackReceiver connection_callback_receiver; + connection.Connect(connection_callback_receiver.callback()); + connection_callback_receiver.WaitForCallback(); + EXPECT_FALSE(connection_callback_receiver.value()); +} + +TEST_F(FidoBleConnectionTest, ReadStatusNotifications) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + TestReadCallback read_callback; + + AddFidoDevice(device_address); + SetupConnectingFidoDevice(device_address); + FidoBleConnection connection(adapter(), device_address, + read_callback.GetCallback()); + + TestConnectionCallbackReceiver connection_callback_receiver; + connection.Connect(connection_callback_receiver.callback()); + connection_callback_receiver.WaitForCallback(); + EXPECT_TRUE(connection_callback_receiver.value()); + + std::vector<uint8_t> payload = ToByteVector("foo"); + NotifyStatusChanged(payload); + EXPECT_EQ(payload, read_callback.WaitForResult()); + + payload = ToByteVector("bar"); + NotifyStatusChanged(payload); + EXPECT_EQ(payload, read_callback.WaitForResult()); +} + +TEST_F(FidoBleConnectionTest, ReadControlPointLength) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + AddFidoDevice(device_address); + SetupConnectingFidoDevice(device_address); + FidoBleConnection connection(adapter(), device_address, + base::DoNothing()); + + TestConnectionCallbackReceiver connection_callback_receiver; + connection.Connect(connection_callback_receiver.callback()); + connection_callback_receiver.WaitForCallback(); + EXPECT_TRUE(connection_callback_receiver.value()); + + { + TestReadControlPointLengthCallback length_callback; + SetNextReadControlPointLengthReponse(false, {}); + connection.ReadControlPointLength(length_callback.callback()); + length_callback.WaitForCallback(); + EXPECT_EQ(base::nullopt, length_callback.value()); + } + + // The Control Point Length should consist of exactly two bytes, hence we + // EXPECT_EQ(base::nullopt) for payloads of size 0, 1 and 3. + { + TestReadControlPointLengthCallback length_callback; + SetNextReadControlPointLengthReponse(true, {}); + connection.ReadControlPointLength(length_callback.callback()); + length_callback.WaitForCallback(); + EXPECT_EQ(base::nullopt, length_callback.value()); + } + + { + TestReadControlPointLengthCallback length_callback; + SetNextReadControlPointLengthReponse(true, {0xAB}); + connection.ReadControlPointLength(length_callback.callback()); + length_callback.WaitForCallback(); + EXPECT_EQ(base::nullopt, length_callback.value()); + } + + { + TestReadControlPointLengthCallback length_callback; + SetNextReadControlPointLengthReponse(true, {0xAB, 0xCD}); + connection.ReadControlPointLength(length_callback.callback()); + length_callback.WaitForCallback(); + EXPECT_EQ(0xABCD, *length_callback.value()); + } + + { + TestReadControlPointLengthCallback length_callback; + SetNextReadControlPointLengthReponse(true, {0xAB, 0xCD, 0xEF}); + connection.ReadControlPointLength(length_callback.callback()); + length_callback.WaitForCallback(); + EXPECT_EQ(base::nullopt, length_callback.value()); + } +} + +TEST_F(FidoBleConnectionTest, WriteControlPoint) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + AddFidoDevice(device_address); + SetupConnectingFidoDevice(device_address); + FidoBleConnection connection(adapter(), device_address, + base::DoNothing()); + + TestConnectionCallbackReceiver connection_callback_receiver; + connection.Connect(connection_callback_receiver.callback()); + connection_callback_receiver.WaitForCallback(); + EXPECT_TRUE(connection_callback_receiver.value()); + + { + TestWriteCallback write_callback; + SetNextWriteControlPointResponse(false); + connection.WriteControlPoint({}, write_callback.callback()); + write_callback.WaitForCallback(); + EXPECT_FALSE(write_callback.value()); + } + + { + TestWriteCallback write_callback; + SetNextWriteControlPointResponse(true); + connection.WriteControlPoint({}, write_callback.callback()); + write_callback.WaitForCallback(); + EXPECT_TRUE(write_callback.value()); + } +} + +TEST_F(FidoBleConnectionTest, ReadsAndWriteFailWhenDisconnected) { + const std::string device_address = BluetoothTest::kTestDeviceAddress1; + + AddFidoDevice(device_address); + SetupConnectingFidoDevice(device_address); + FidoBleConnection connection(adapter(), device_address, + base::DoNothing()); + + SimulateGattConnectionError(); + TestConnectionCallbackReceiver connection_callback_receiver; + connection.Connect(connection_callback_receiver.callback()); + connection_callback_receiver.WaitForCallback(); + EXPECT_FALSE(connection_callback_receiver.value()); + + // Reads should always fail on a disconnected device. + TestReadControlPointLengthCallback length_callback; + connection.ReadControlPointLength(length_callback.callback()); + length_callback.WaitForCallback(); + EXPECT_EQ(base::nullopt, length_callback.value()); + + // Writes should always fail on a disconnected device. + TestWriteCallback write_callback; + connection.WriteControlPoint({}, write_callback.callback()); + write_callback.WaitForCallback(); + EXPECT_FALSE(write_callback.value()); +} + +} // namespace device diff --git a/chromium/device/fido/fido_ble_device.cc b/chromium/device/fido/ble/fido_ble_device.cc index e292afb9a11..89b7a1733dc 100644 --- a/chromium/device/fido/fido_ble_device.cc +++ b/chromium/device/fido/ble/fido_ble_device.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fido_ble_device.h" +#include "device/fido/ble/fido_ble_device.h" #include <bitset> @@ -10,17 +10,16 @@ #include "base/strings/string_piece.h" #include "components/apdu/apdu_response.h" #include "device/bluetooth/bluetooth_uuid.h" -#include "device/fido/fido_ble_frames.h" -#include "device/fido/fido_ble_uuids.h" +#include "device/fido/ble/fido_ble_frames.h" +#include "device/fido/ble/fido_ble_uuids.h" #include "device/fido/fido_constants.h" namespace device { -FidoBleDevice::FidoBleDevice(std::string address) : weak_factory_(this) { +FidoBleDevice::FidoBleDevice(BluetoothAdapter* adapter, std::string address) + : weak_factory_(this) { connection_ = std::make_unique<FidoBleConnection>( - std::move(address), - base::BindRepeating(&FidoBleDevice::OnConnectionStatus, - base::Unretained(this)), + adapter, std::move(address), base::BindRepeating(&FidoBleDevice::OnStatusMessage, base::Unretained(this))); } @@ -36,7 +35,8 @@ void FidoBleDevice::Connect() { StartTimeout(); state_ = State::kBusy; - connection_->Connect(); + connection_->Connect( + base::BindOnce(&FidoBleDevice::OnConnected, base::Unretained(this))); } void FidoBleDevice::SendPing(std::vector<uint8_t> data, @@ -67,6 +67,10 @@ std::string FidoBleDevice::GetId() const { return GetId(connection_->address()); } +FidoTransportProtocol FidoBleDevice::DeviceTransport() const { + return FidoTransportProtocol::kBluetoothLowEnergy; +} + bool FidoBleDevice::IsInPairingMode() const { const BluetoothDevice* const ble_device = connection_->GetBleDevice(); if (!ble_device) @@ -96,12 +100,6 @@ bool FidoBleDevice::IsInPairingMode() const { static_cast<int>(FidoServiceDataFlags::kPairingMode)) != 0; } -FidoBleConnection::ConnectionStatusCallback -FidoBleDevice::GetConnectionStatusCallbackForTesting() { - return base::BindRepeating(&FidoBleDevice::OnConnectionStatus, - base::Unretained(this)); -} - FidoBleConnection::ReadCallback FidoBleDevice::GetReadCallbackForTesting() { return base::BindRepeating(&FidoBleDevice::OnStatusMessage, base::Unretained(this)); @@ -156,6 +154,7 @@ void FidoBleDevice::Transition() { break; case State::kBusy: break; + case State::kMsgError: case State::kDeviceError: auto self = GetWeakPtr(); // Executing callbacks may free |this|. Check |self| first. @@ -174,16 +173,13 @@ void FidoBleDevice::AddToPendingFrames(FidoBleDeviceCommand cmd, DeviceCallback callback) { pending_frames_.emplace( FidoBleFrame(cmd, std::move(request)), - base::BindOnce( - [](DeviceCallback callback, base::Optional<FidoBleFrame> frame) { - std::move(callback).Run(frame ? base::make_optional(frame->data()) - : base::nullopt); - }, - std::move(callback))); + base::BindOnce(&FidoBleDevice::OnBleResponseReceived, + weak_factory_.GetWeakPtr(), std::move(callback))); + Transition(); } -void FidoBleDevice::OnConnectionStatus(bool success) { +void FidoBleDevice::OnConnected(bool success) { StopTimeout(); state_ = success ? State::kConnected : State::kDeviceError; Transition(); @@ -227,4 +223,36 @@ void FidoBleDevice::OnTimeout() { state_ = State::kDeviceError; } +void FidoBleDevice::OnBleResponseReceived(DeviceCallback callback, + base::Optional<FidoBleFrame> frame) { + if (!frame || !frame->IsValid()) { + state_ = State::kDeviceError; + std::move(callback).Run(base::nullopt); + return; + } + + if (frame->command() == FidoBleDeviceCommand::kError) { + ProcessBleDeviceError(frame->data()); + std::move(callback).Run(base::nullopt); + return; + } + + std::move(callback).Run(frame->data()); +} + +void FidoBleDevice::ProcessBleDeviceError(base::span<const uint8_t> data) { + DCHECK_EQ(1u, data.size()); + const auto error_constant = data[0]; + if (error_constant == + base::strict_cast<uint8_t>(FidoBleFrame::ErrorCode::INVALID_CMD) || + error_constant == + base::strict_cast<uint8_t>(FidoBleFrame::ErrorCode::INVALID_PAR) || + error_constant == + base::strict_cast<uint8_t>(FidoBleFrame::ErrorCode::INVALID_LEN)) { + state_ = State::kMsgError; + } else { + state_ = State::kDeviceError; + } +} + } // namespace device diff --git a/chromium/device/fido/fido_ble_device.h b/chromium/device/fido/ble/fido_ble_device.h index 41d617e2b55..1c62bf46d7c 100644 --- a/chromium/device/fido/fido_ble_device.h +++ b/chromium/device/fido/ble/fido_ble_device.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef DEVICE_FIDO_FIDO_BLE_DEVICE_H_ -#define DEVICE_FIDO_FIDO_BLE_DEVICE_H_ +#ifndef DEVICE_FIDO_BLE_FIDO_BLE_DEVICE_H_ +#define DEVICE_FIDO_BLE_FIDO_BLE_DEVICE_H_ #include <memory> #include <string> @@ -17,19 +17,20 @@ #include "base/optional.h" #include "base/strings/string_piece.h" #include "base/timer/timer.h" -#include "device/fido/fido_ble_connection.h" -#include "device/fido/fido_ble_transaction.h" +#include "device/fido/ble/fido_ble_connection.h" +#include "device/fido/ble/fido_ble_transaction.h" #include "device/fido/fido_constants.h" #include "device/fido/fido_device.h" namespace device { +class BluetoothAdapter; class FidoBleFrame; class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleDevice : public FidoDevice { public: using FrameCallback = FidoBleTransaction::FrameCallback; - explicit FidoBleDevice(std::string address); + FidoBleDevice(BluetoothAdapter* adapter, std::string address); explicit FidoBleDevice(std::unique_ptr<FidoBleConnection> connection); ~FidoBleDevice() override; @@ -41,13 +42,12 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleDevice : public FidoDevice { void TryWink(WinkCallback callback) override; void Cancel() override; std::string GetId() const override; + FidoTransportProtocol DeviceTransport() const override; // Returns whether or not the underlying BLE device is currently in pairing // mode by investigating the advertisement payload. bool IsInPairingMode() const; - FidoBleConnection::ConnectionStatusCallback - GetConnectionStatusCallbackForTesting(); FidoBleConnection::ReadCallback GetReadCallbackForTesting(); protected: @@ -65,7 +65,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleDevice : public FidoDevice { void ResetTransaction(); private: - void OnConnectionStatus(bool success); + void OnConnected(bool success); void OnStatusMessage(std::vector<uint8_t> data); void ReadControlPointLength(); @@ -78,6 +78,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleDevice : public FidoDevice { void StopTimeout(); void OnTimeout(); + void OnBleResponseReceived(DeviceCallback callback, + base::Optional<FidoBleFrame> frame); + void ProcessBleDeviceError(base::span<const uint8_t> data); + base::OneShotTimer timer_; std::unique_ptr<FidoBleConnection> connection_; @@ -93,4 +97,4 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleDevice : public FidoDevice { } // namespace device -#endif // DEVICE_FIDO_FIDO_BLE_DEVICE_H_ +#endif // DEVICE_FIDO_BLE_FIDO_BLE_DEVICE_H_ diff --git a/chromium/device/fido/fido_ble_device_unittest.cc b/chromium/device/fido/ble/fido_ble_device_unittest.cc index 4632da30f09..190c2e17b8a 100644 --- a/chromium/device/fido/fido_ble_device_unittest.cc +++ b/chromium/device/fido/ble/fido_ble_device_unittest.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fido_ble_device.h" +#include "device/fido/ble/fido_ble_device.h" #include "base/optional.h" #include "base/test/bind_test_util.h" @@ -11,10 +11,10 @@ #include "device/bluetooth/test/bluetooth_test.h" #include "device/bluetooth/test/mock_bluetooth_adapter.h" #include "device/bluetooth/test/mock_bluetooth_device.h" -#include "device/fido/fido_ble_uuids.h" +#include "device/fido/ble/fido_ble_uuids.h" +#include "device/fido/ble/mock_fido_ble_connection.h" #include "device/fido/fido_constants.h" #include "device/fido/fido_parsing_utils.h" -#include "device/fido/mock_fido_ble_connection.h" #include "device/fido/test_callback_receiver.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -75,20 +75,19 @@ class FidoBleDeviceTest : public Test { public: FidoBleDeviceTest() { auto connection = std::make_unique<MockFidoBleConnection>( - BluetoothTestBase::kTestDeviceAddress1); + adapter_.get(), BluetoothTestBase::kTestDeviceAddress1); connection_ = connection.get(); device_ = std::make_unique<FidoBleDevice>(std::move(connection)); - connection_->connection_status_callback() = - device_->GetConnectionStatusCallbackForTesting(); connection_->read_callback() = device_->GetReadCallbackForTesting(); } + MockBluetoothAdapter* adapter() { return adapter_.get(); } FidoBleDevice* device() { return device_.get(); } MockFidoBleConnection* connection() { return connection_; } void ConnectWithLength(uint16_t length) { - EXPECT_CALL(*connection(), Connect()).WillOnce(Invoke([this] { - connection()->connection_status_callback().Run(true); + EXPECT_CALL(*connection(), ConnectPtr).WillOnce(Invoke([](auto* callback) { + std::move(*callback).Run(true); })); EXPECT_CALL(*connection(), ReadControlPointLengthPtr(_)) @@ -102,14 +101,17 @@ class FidoBleDeviceTest : public Test { base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME}; private: + scoped_refptr<MockBluetoothAdapter> adapter_ = + base::MakeRefCounted<NiceMockBluetoothAdapter>(); MockFidoBleConnection* connection_; std::unique_ptr<FidoBleDevice> device_; }; TEST_F(FidoBleDeviceTest, ConnectionFailureTest) { - EXPECT_CALL(*connection(), Connect()).WillOnce(Invoke([this] { - connection()->connection_status_callback().Run(false); + EXPECT_CALL(*connection(), ConnectPtr).WillOnce(Invoke([](auto* callback) { + std::move(*callback).Run(false); })); + device()->Connect(); } @@ -223,23 +225,21 @@ TEST_F(FidoBleDeviceTest, IsInPairingMode) { // By default, a device is not in pairing mode. EXPECT_FALSE(device()->IsInPairingMode()); - // Initiate default connection behavior, which will attempt to obtain an - // adapter. - auto mock_adapter = base::MakeRefCounted<NiceMockBluetoothAdapter>(); - BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); - EXPECT_CALL(*connection(), Connect()).WillOnce(Invoke([this] { - connection()->FidoBleConnection::Connect(); - })); + // Initiate default connection behavior. + EXPECT_CALL(*connection(), ConnectPtr) + .WillOnce(Invoke([this](auto* callback) { + connection()->FidoBleConnection::Connect(std::move(*callback)); + })); device()->Connect(); // Add a mock fido device. This should also not be considered to be in pairing // mode. auto mock_bluetooth_device = std::make_unique<NiceMockBluetoothDevice>( - mock_adapter.get(), /* bluetooth_class */ 0u, + adapter(), /* bluetooth_class */ 0u, BluetoothTestBase::kTestDeviceNameU2f, BluetoothTestBase::kTestDeviceAddress1, /* paired */ true, /* connected */ false); - EXPECT_CALL(*mock_adapter, GetDevice(BluetoothTestBase::kTestDeviceAddress1)) + EXPECT_CALL(*adapter(), GetDevice(BluetoothTestBase::kTestDeviceAddress1)) .WillRepeatedly(Return(mock_bluetooth_device.get())); EXPECT_FALSE(device()->IsInPairingMode()); @@ -283,4 +283,29 @@ TEST_F(FidoBleDeviceTest, IsInPairingMode) { EXPECT_FALSE(device()->IsInPairingMode()); } +TEST_F(FidoBleDeviceTest, DeviceMsgErrorTest) { + // kError(BF), followed by payload length(0001), followed by INVALID_CMD(01). + constexpr uint8_t kBleInvalidCommandError[] = {0xbf, 0x00, 0x01, 0x01}; + ConnectWithLength(kControlPointLength); + + EXPECT_CALL(*connection(), WriteControlPointPtr(_, _)) + .WillOnce(::testing::WithArg<1>( + Invoke([this, kBleInvalidCommandError](auto* cb) { + scoped_task_environment_.GetMainThreadTaskRunner()->PostTask( + FROM_HERE, base::BindOnce(std::move(*cb), true)); + + scoped_task_environment_.GetMainThreadTaskRunner()->PostTask( + FROM_HERE, base::BindOnce(connection()->read_callback(), + fido_parsing_utils::Materialize( + kBleInvalidCommandError))); + }))); + + TestDeviceCallbackReceiver callback_receiver; + const auto payload = fido_parsing_utils::Materialize(kTestData); + device()->SendPing(payload, callback_receiver.callback()); + + callback_receiver.WaitForCallback(); + EXPECT_EQ(FidoDevice::State::kMsgError, device()->state()); +} + } // namespace device diff --git a/chromium/device/fido/fido_ble_discovery.cc b/chromium/device/fido/ble/fido_ble_discovery.cc index 16de4ad732f..65270699888 100644 --- a/chromium/device/fido/fido_ble_discovery.cc +++ b/chromium/device/fido/ble/fido_ble_discovery.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fido_ble_discovery.h" +#include "device/fido/ble/fido_ble_discovery.h" #include <utility> @@ -12,12 +12,14 @@ #include "device/bluetooth/bluetooth_common.h" #include "device/bluetooth/bluetooth_discovery_session.h" #include "device/bluetooth/bluetooth_uuid.h" -#include "device/fido/fido_ble_device.h" -#include "device/fido/fido_ble_uuids.h" +#include "device/fido/ble/fido_ble_device.h" +#include "device/fido/ble/fido_ble_uuids.h" namespace device { -FidoBleDiscovery::FidoBleDiscovery() : weak_factory_(this) {} +FidoBleDiscovery::FidoBleDiscovery() + : FidoBleDiscoveryBase(FidoTransportProtocol::kBluetoothLowEnergy), + weak_factory_(this) {} FidoBleDiscovery::~FidoBleDiscovery() = default; @@ -32,9 +34,11 @@ void FidoBleDiscovery::OnSetPowered() { VLOG(2) << "Adapter " << adapter()->GetAddress() << " is powered on."; for (BluetoothDevice* device : adapter()->GetDevices()) { - if (base::ContainsKey(device->GetUUIDs(), FidoServiceUUID())) { + if (!CheckForExcludedDeviceAndCacheAddress(device) && + base::ContainsKey(device->GetUUIDs(), FidoServiceUUID())) { VLOG(2) << "U2F BLE device: " << device->GetAddress(); - AddDevice(std::make_unique<FidoBleDevice>(device->GetAddress())); + AddDevice( + std::make_unique<FidoBleDevice>(adapter(), device->GetAddress())); } } @@ -54,19 +58,21 @@ void FidoBleDiscovery::OnSetPowered() { void FidoBleDiscovery::DeviceAdded(BluetoothAdapter* adapter, BluetoothDevice* device) { - if (base::ContainsKey(device->GetUUIDs(), FidoServiceUUID())) { + if (!CheckForExcludedDeviceAndCacheAddress(device) && + base::ContainsKey(device->GetUUIDs(), FidoServiceUUID())) { VLOG(2) << "Discovered U2F BLE device: " << device->GetAddress(); - AddDevice(std::make_unique<FidoBleDevice>(device->GetAddress())); + AddDevice(std::make_unique<FidoBleDevice>(adapter, device->GetAddress())); } } void FidoBleDiscovery::DeviceChanged(BluetoothAdapter* adapter, BluetoothDevice* device) { - if (base::ContainsKey(device->GetUUIDs(), FidoServiceUUID()) && + if (!CheckForExcludedDeviceAndCacheAddress(device) && + base::ContainsKey(device->GetUUIDs(), FidoServiceUUID()) && !GetDevice(FidoBleDevice::GetId(device->GetAddress()))) { VLOG(2) << "Discovered U2F service on existing BLE device: " << device->GetAddress(); - AddDevice(std::make_unique<FidoBleDevice>(device->GetAddress())); + AddDevice(std::make_unique<FidoBleDevice>(adapter, device->GetAddress())); } } @@ -78,4 +84,35 @@ void FidoBleDiscovery::DeviceRemoved(BluetoothAdapter* adapter, } } +void FidoBleDiscovery::AdapterPoweredChanged(BluetoothAdapter* adapter, + bool powered) { + // If Bluetooth adapter is powered on, resume scanning for nearby FIDO + // devices. Previously inactive discovery sessions would be terminated upon + // invocation of OnSetPowered(). + if (powered) + OnSetPowered(); +} + +bool FidoBleDiscovery::CheckForExcludedDeviceAndCacheAddress( + const BluetoothDevice* device) { + std::string device_address = device->GetAddress(); + auto address_position = + excluded_cable_device_addresses_.lower_bound(device_address); + if (address_position != excluded_cable_device_addresses_.end() && + *address_position == device_address) { + return true; + } + + // IsCableDevice() is not stable, and can change throughout the lifetime. As + // so, cache device address for known Cable devices so that we do not attempt + // to connect to these devices. + if (IsCableDevice(device)) { + excluded_cable_device_addresses_.insert(address_position, + std::move(device_address)); + return true; + } + + return false; +} + } // namespace device diff --git a/chromium/device/fido/fido_ble_discovery.h b/chromium/device/fido/ble/fido_ble_discovery.h index abf6f27476f..cc804cf38c3 100644 --- a/chromium/device/fido/fido_ble_discovery.h +++ b/chromium/device/fido/ble/fido_ble_discovery.h @@ -2,15 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef DEVICE_FIDO_FIDO_BLE_DISCOVERY_H_ -#define DEVICE_FIDO_FIDO_BLE_DISCOVERY_H_ +#ifndef DEVICE_FIDO_BLE_FIDO_BLE_DISCOVERY_H_ +#define DEVICE_FIDO_BLE_FIDO_BLE_DISCOVERY_H_ #include <memory> +#include <set> +#include <string> #include "base/component_export.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" -#include "device/fido/fido_ble_discovery_base.h" +#include "device/fido/ble/fido_ble_discovery_base.h" namespace device { @@ -35,7 +37,13 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleDiscovery BluetoothDevice* device) override; void DeviceRemoved(BluetoothAdapter* adapter, BluetoothDevice* device) override; + void AdapterPoweredChanged(BluetoothAdapter* adapter, bool powered) override; + // Returns true if |device| is a Cable device. If so, add address of |device| + // to |blacklisted_cable_device_addresses_|. + bool CheckForExcludedDeviceAndCacheAddress(const BluetoothDevice* device); + + std::set<std::string> excluded_cable_device_addresses_; base::WeakPtrFactory<FidoBleDiscovery> weak_factory_; DISALLOW_COPY_AND_ASSIGN(FidoBleDiscovery); @@ -43,4 +51,4 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleDiscovery } // namespace device -#endif // DEVICE_FIDO_FIDO_BLE_DISCOVERY_H_ +#endif // DEVICE_FIDO_BLE_FIDO_BLE_DISCOVERY_H_ diff --git a/chromium/device/fido/fido_ble_discovery_base.cc b/chromium/device/fido/ble/fido_ble_discovery_base.cc index b118bc1e1e7..6ef5475d1c7 100644 --- a/chromium/device/fido/fido_ble_discovery_base.cc +++ b/chromium/device/fido/ble/fido_ble_discovery_base.cc @@ -2,24 +2,25 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fido_ble_discovery_base.h" +#include "device/fido/ble/fido_ble_discovery_base.h" #include <utility> #include "base/bind.h" #include "base/callback_helpers.h" #include "base/location.h" +#include "base/no_destructor.h" #include "base/threading/thread_task_runner_handle.h" #include "build/build_config.h" #include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/bluetooth/bluetooth_common.h" #include "device/bluetooth/bluetooth_discovery_session.h" +#include "device/fido/ble/fido_ble_uuids.h" namespace device { -FidoBleDiscoveryBase::FidoBleDiscoveryBase() - : FidoDiscovery(FidoTransportProtocol::kBluetoothLowEnergy), - weak_factory_(this) {} +FidoBleDiscoveryBase::FidoBleDiscoveryBase(FidoTransportProtocol transport) + : FidoDiscovery(transport), weak_factory_(this) {} FidoBleDiscoveryBase::~FidoBleDiscoveryBase() { if (adapter_) @@ -28,6 +29,13 @@ FidoBleDiscoveryBase::~FidoBleDiscoveryBase() { // Destroying |discovery_session_| will best-effort-stop discovering. } +// static +const BluetoothUUID& FidoBleDiscoveryBase::CableAdvertisementUUID() { + static const base::NoDestructor<BluetoothUUID> service_uuid( + kCableAdvertisementUUID128); + return *service_uuid; +} + void FidoBleDiscoveryBase::OnStartDiscoverySessionWithFilter( std::unique_ptr<BluetoothDiscoverySession> session) { SetDiscoverySession(std::move(session)); @@ -50,25 +58,28 @@ void FidoBleDiscoveryBase::SetDiscoverySession( discovery_session_ = std::move(discovery_session); } +bool FidoBleDiscoveryBase::IsCableDevice(const BluetoothDevice* device) const { + const auto& uuid = CableAdvertisementUUID(); + return base::ContainsKey(device->GetServiceData(), uuid) || + base::ContainsKey(device->GetUUIDs(), uuid); +} + void FidoBleDiscoveryBase::OnGetAdapter( scoped_refptr<BluetoothAdapter> adapter) { + if (!adapter->IsPresent()) { + DVLOG(2) << "bluetooth adapter is not available in current system."; + NotifyDiscoveryStarted(false); + return; + } + DCHECK(!adapter_); adapter_ = std::move(adapter); DCHECK(adapter_); - VLOG(2) << "Got adapter " << adapter_->GetAddress(); + DVLOG(2) << "Got adapter " << adapter_->GetAddress(); adapter_->AddObserver(this); - if (adapter_->IsPowered()) { + if (adapter_->IsPowered()) OnSetPowered(); - } else { - adapter_->SetPowered( - true, - base::AdaptCallbackForRepeating(base::BindOnce( - &FidoBleDiscoveryBase::OnSetPowered, weak_factory_.GetWeakPtr())), - base::AdaptCallbackForRepeating( - base::BindOnce(&FidoBleDiscoveryBase::OnSetPoweredError, - weak_factory_.GetWeakPtr()))); - } } void FidoBleDiscoveryBase::StartInternal() { diff --git a/chromium/device/fido/fido_ble_discovery_base.h b/chromium/device/fido/ble/fido_ble_discovery_base.h index dd4a0f14e54..3a1f64c4b3a 100644 --- a/chromium/device/fido/fido_ble_discovery_base.h +++ b/chromium/device/fido/ble/fido_ble_discovery_base.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef DEVICE_FIDO_FIDO_BLE_DISCOVERY_BASE_H_ -#define DEVICE_FIDO_FIDO_BLE_DISCOVERY_BASE_H_ +#ifndef DEVICE_FIDO_BLE_FIDO_BLE_DISCOVERY_BASE_H_ +#define DEVICE_FIDO_BLE_FIDO_BLE_DISCOVERY_BASE_H_ #include <memory> @@ -12,6 +12,7 @@ #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" #include "device/bluetooth/bluetooth_adapter.h" +#include "device/bluetooth/bluetooth_uuid.h" #include "device/fido/fido_discovery.h" namespace device { @@ -22,10 +23,12 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleDiscoveryBase : public FidoDiscovery, public BluetoothAdapter::Observer { public: - FidoBleDiscoveryBase(); + explicit FidoBleDiscoveryBase(FidoTransportProtocol transport); ~FidoBleDiscoveryBase() override; protected: + static const BluetoothUUID& CableAdvertisementUUID(); + virtual void OnSetPowered() = 0; virtual void OnStartDiscoverySessionWithFilter( std::unique_ptr<BluetoothDiscoverySession>); @@ -34,6 +37,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleDiscoveryBase void OnStartDiscoverySessionError(); void SetDiscoverySession( std::unique_ptr<BluetoothDiscoverySession> discovery_session); + bool IsCableDevice(const BluetoothDevice* device) const; BluetoothAdapter* adapter() { return adapter_.get(); } @@ -53,4 +57,4 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleDiscoveryBase } // namespace device -#endif // DEVICE_FIDO_FIDO_BLE_DISCOVERY_BASE_H_ +#endif // DEVICE_FIDO_BLE_FIDO_BLE_DISCOVERY_BASE_H_ diff --git a/chromium/device/fido/fido_ble_discovery_unittest.cc b/chromium/device/fido/ble/fido_ble_discovery_unittest.cc index 89f689a43a1..74efa866290 100644 --- a/chromium/device/fido/fido_ble_discovery_unittest.cc +++ b/chromium/device/fido/ble/fido_ble_discovery_unittest.cc @@ -2,15 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fido_ble_discovery.h" +#include "device/fido/ble/fido_ble_discovery.h" #include <string> #include "base/bind.h" #include "base/run_loop.h" #include "build/build_config.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/bluetooth/test/bluetooth_test.h" -#include "device/fido/fido_ble_device.h" +#include "device/bluetooth/test/mock_bluetooth_adapter.h" +#include "device/fido/ble/fido_ble_device.h" #include "device/fido/mock_fido_discovery_observer.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -37,6 +39,36 @@ MATCHER_P(IdMatches, id, "") { return arg->GetId() == std::string("ble:") + id; } +TEST_F(BluetoothTest, FidoBleDiscoveryNotifyObserverWhenAdapterNotPresent) { + FidoBleDiscovery discovery; + MockFidoDiscoveryObserver observer; + discovery.set_observer(&observer); + auto mock_adapter = + base::MakeRefCounted<::testing::NiceMock<MockBluetoothAdapter>>(); + EXPECT_CALL(*mock_adapter, IsPresent()).WillOnce(::testing::Return(false)); + EXPECT_CALL(*mock_adapter, SetPowered).Times(0); + BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); + EXPECT_CALL(observer, DiscoveryStarted(&discovery, false)); + discovery.Start(); +} + +TEST_F(BluetoothTest, FidoBleDiscoveryResumeScanningAfterPoweredOn) { + FidoBleDiscovery discovery; + MockFidoDiscoveryObserver observer; + discovery.set_observer(&observer); + auto mock_adapter = + base::MakeRefCounted<::testing::NiceMock<MockBluetoothAdapter>>(); + EXPECT_CALL(*mock_adapter, IsPresent()).WillOnce(::testing::Return(true)); + EXPECT_CALL(*mock_adapter, IsPowered()).WillOnce(::testing::Return(false)); + + // After BluetoothAdapter is powered on, we expect that discovery session + // starts again. + EXPECT_CALL(*mock_adapter, StartDiscoverySessionWithFilterRaw); + BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); + discovery.Start(); + mock_adapter->NotifyAdapterPoweredChanged(true); +} + TEST_F(BluetoothTest, FidoBleDiscoveryNoAdapter) { // We purposefully construct a temporary and provide no fake adapter, // simulating cases where the discovery is destroyed before obtaining a handle @@ -166,4 +198,38 @@ TEST_F(BluetoothTest, FidoBleDiscoveryFindsUpdatedDevice) { } } +TEST_F(BluetoothTest, FidoBleDiscoveryRejectsCableDevice) { + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + InitWithFakeAdapter(); + + FidoBleDiscovery discovery; + MockFidoDiscoveryObserver observer; + discovery.set_observer(&observer); + + { + base::RunLoop run_loop; + auto quit = run_loop.QuitClosure(); + EXPECT_CALL(observer, DiscoveryStarted(&discovery, true)) + .WillOnce(ReturnFromAsyncCall(quit)); + + discovery.Start(); + run_loop.Run(); + } + + EXPECT_CALL(observer, DeviceAdded(&discovery, _)).Times(0); + + // Simulates a discovery of two Cable devices one of which is an Android Cable + // authenticator and other is IOS Cable authenticator. + SimulateLowEnergyDevice(8); + SimulateLowEnergyDevice(9); + + // Simulates a device change update received from the BluetoothAdapter. As the + // updated device has an address that we know is an Cable device, this should + // not trigger DeviceAdded(). + SimulateLowEnergyDevice(7); +} + } // namespace device diff --git a/chromium/device/fido/fido_ble_frames.cc b/chromium/device/fido/ble/fido_ble_frames.cc index 411b560d1ef..0c50b567a7c 100644 --- a/chromium/device/fido/fido_ble_frames.cc +++ b/chromium/device/fido/ble/fido_ble_frames.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fido_ble_frames.h" +#include "device/fido/ble/fido_ble_frames.h" #include <algorithm> #include <limits> diff --git a/chromium/device/fido/fido_ble_frames.h b/chromium/device/fido/ble/fido_ble_frames.h index d326251caa7..ea4d16ac784 100644 --- a/chromium/device/fido/fido_ble_frames.h +++ b/chromium/device/fido/ble/fido_ble_frames.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef DEVICE_FIDO_FIDO_BLE_FRAMES_H_ -#define DEVICE_FIDO_FIDO_BLE_FRAMES_H_ +#ifndef DEVICE_FIDO_BLE_FIDO_BLE_FRAMES_H_ +#define DEVICE_FIDO_BLE_FIDO_BLE_FRAMES_H_ #include <stdint.h> @@ -181,4 +181,4 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleFrameAssembler { } // namespace device -#endif // DEVICE_FIDO_FIDO_BLE_FRAMES_H_ +#endif // DEVICE_FIDO_BLE_FIDO_BLE_FRAMES_H_ diff --git a/chromium/device/fido/fido_ble_frames_fuzzer.cc b/chromium/device/fido/ble/fido_ble_frames_fuzzer.cc index 1e3627dba0b..63b86064d17 100644 --- a/chromium/device/fido/fido_ble_frames_fuzzer.cc +++ b/chromium/device/fido/ble/fido_ble_frames_fuzzer.cc @@ -7,7 +7,7 @@ #include <vector> -#include "device/fido/fido_ble_frames.h" +#include "device/fido/ble/fido_ble_frames.h" #include "device/fido/fido_constants.h" extern "C" int LLVMFuzzerTestOneInput(const uint8_t* raw_data, size_t size) { diff --git a/chromium/device/fido/fido_ble_frames_unittest.cc b/chromium/device/fido/ble/fido_ble_frames_unittest.cc index 226c58f0d9b..2d20c8d5370 100644 --- a/chromium/device/fido/fido_ble_frames_unittest.cc +++ b/chromium/device/fido/ble/fido_ble_frames_unittest.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fido_ble_frames.h" +#include "device/fido/ble/fido_ble_frames.h" #include <vector> diff --git a/chromium/device/fido/fido_ble_transaction.cc b/chromium/device/fido/ble/fido_ble_transaction.cc index 578395b0abf..59ac87697c6 100644 --- a/chromium/device/fido/fido_ble_transaction.cc +++ b/chromium/device/fido/ble/fido_ble_transaction.cc @@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fido_ble_transaction.h" +#include "device/fido/ble/fido_ble_transaction.h" #include <utility> -#include "device/fido/fido_ble_connection.h" +#include "device/fido/ble/fido_ble_connection.h" #include "device/fido/fido_constants.h" namespace device { @@ -50,7 +50,7 @@ void FidoBleTransaction::WriteRequestFragment( void FidoBleTransaction::OnRequestFragmentWritten(bool success) { StopTimeout(); if (!success) { - OnError(); + OnError(base::nullopt); return; } @@ -72,7 +72,7 @@ void FidoBleTransaction::OnResponseFragment(std::vector<uint8_t> data) { FidoBleFrameInitializationFragment fragment; if (!FidoBleFrameInitializationFragment::Parse(data, &fragment)) { DLOG(ERROR) << "Malformed Frame Initialization Fragment"; - OnError(); + OnError(base::nullopt); return; } @@ -81,7 +81,7 @@ void FidoBleTransaction::OnResponseFragment(std::vector<uint8_t> data) { FidoBleFrameContinuationFragment fragment; if (!FidoBleFrameContinuationFragment::Parse(data, &fragment)) { DLOG(ERROR) << "Malformed Frame Continuation Fragment"; - OnError(); + OnError(base::nullopt); return; } @@ -118,22 +118,26 @@ void FidoBleTransaction::ProcessResponseFrame(FidoBleFrame response_frame) { DCHECK_EQ(response_frame.command(), FidoBleDeviceCommand::kError); DLOG(ERROR) << "CMD_ERROR: " << static_cast<uint8_t>(response_frame.GetErrorCode()); - OnError(); + OnError(response_frame.IsValid() + ? base::make_optional(std::move(response_frame)) + : base::nullopt); } void FidoBleTransaction::StartTimeout() { - timer_.Start(FROM_HERE, kDeviceTimeout, this, &FidoBleTransaction::OnError); + timer_.Start(FROM_HERE, kDeviceTimeout, + base::BindOnce(&FidoBleTransaction::OnError, + base::Unretained(this), base::nullopt)); } void FidoBleTransaction::StopTimeout() { timer_.Stop(); } -void FidoBleTransaction::OnError() { +void FidoBleTransaction::OnError(base::Optional<FidoBleFrame> response_frame) { request_frame_.reset(); request_cont_fragments_ = base::queue<FidoBleFrameContinuationFragment>(); response_frame_assembler_.reset(); - std::move(callback_).Run(base::nullopt); + std::move(callback_).Run(std::move(response_frame)); } } // namespace device diff --git a/chromium/device/fido/fido_ble_transaction.h b/chromium/device/fido/ble/fido_ble_transaction.h index d316086d687..a2d585da707 100644 --- a/chromium/device/fido/fido_ble_transaction.h +++ b/chromium/device/fido/ble/fido_ble_transaction.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef DEVICE_FIDO_FIDO_BLE_TRANSACTION_H_ -#define DEVICE_FIDO_FIDO_BLE_TRANSACTION_H_ +#ifndef DEVICE_FIDO_BLE_FIDO_BLE_TRANSACTION_H_ +#define DEVICE_FIDO_BLE_FIDO_BLE_TRANSACTION_H_ #include <memory> #include <vector> @@ -13,7 +13,7 @@ #include "base/memory/weak_ptr.h" #include "base/optional.h" #include "base/timer/timer.h" -#include "device/fido/fido_ble_frames.h" +#include "device/fido/ble/fido_ble_frames.h" namespace device { @@ -40,7 +40,7 @@ class FidoBleTransaction { void StartTimeout(); void StopTimeout(); - void OnError(); + void OnError(base::Optional<FidoBleFrame> response_frame); FidoBleConnection* connection_; uint16_t control_point_length_; @@ -61,4 +61,4 @@ class FidoBleTransaction { } // namespace device -#endif // DEVICE_FIDO_FIDO_BLE_TRANSACTION_H_ +#endif // DEVICE_FIDO_BLE_FIDO_BLE_TRANSACTION_H_ diff --git a/chromium/device/fido/fido_ble_uuids.cc b/chromium/device/fido/ble/fido_ble_uuids.cc index 2f96f75d4dc..4e2c5438817 100644 --- a/chromium/device/fido/fido_ble_uuids.cc +++ b/chromium/device/fido/ble/fido_ble_uuids.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fido_ble_uuids.h" +#include "device/fido/ble/fido_ble_uuids.h" #include "build/build_config.h" @@ -17,10 +17,8 @@ const char kFidoServiceRevisionUUID[] = "00002a28-0000-1000-8000-00805f9b34fb"; const char kFidoServiceRevisionBitfieldUUID[] = "f1d0fff4-deaa-ecee-b42f-c9ba7ed623bb"; -#if defined(OS_MACOSX) -const char kCableAdvertisementUUID[] = "fde2"; -#else -const char kCableAdvertisementUUID[] = "0000fde2-0000-1000-8000-00805f9b34fb"; -#endif +const char kCableAdvertisementUUID16[] = "fde2"; +const char kCableAdvertisementUUID128[] = + "0000fde2-0000-1000-8000-00805f9b34fb"; } // namespace device diff --git a/chromium/device/fido/fido_ble_uuids.h b/chromium/device/fido/ble/fido_ble_uuids.h index a2ead64b534..a608c74ccc7 100644 --- a/chromium/device/fido/fido_ble_uuids.h +++ b/chromium/device/fido/ble/fido_ble_uuids.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef DEVICE_FIDO_FIDO_BLE_UUIDS_H_ -#define DEVICE_FIDO_FIDO_BLE_UUIDS_H_ +#ifndef DEVICE_FIDO_BLE_FIDO_BLE_UUIDS_H_ +#define DEVICE_FIDO_BLE_FIDO_BLE_UUIDS_H_ #include "base/component_export.h" @@ -24,8 +24,9 @@ COMPONENT_EXPORT(DEVICE_FIDO) extern const char kFidoServiceRevisionBitfieldUUID[]; // TODO(hongjunchoi): Add URL to the specification once CaBLE protocol is // standardized. -COMPONENT_EXPORT(DEVICE_FIDO) extern const char kCableAdvertisementUUID[]; +COMPONENT_EXPORT(DEVICE_FIDO) extern const char kCableAdvertisementUUID16[]; +COMPONENT_EXPORT(DEVICE_FIDO) extern const char kCableAdvertisementUUID128[]; } // namespace device -#endif // DEVICE_FIDO_FIDO_BLE_UUIDS_H_ +#endif // DEVICE_FIDO_BLE_FIDO_BLE_UUIDS_H_ diff --git a/chromium/device/fido/mock_fido_ble_connection.cc b/chromium/device/fido/ble/mock_fido_ble_connection.cc index c9bbab33e40..a8c0573e9a0 100644 --- a/chromium/device/fido/mock_fido_ble_connection.cc +++ b/chromium/device/fido/ble/mock_fido_ble_connection.cc @@ -2,36 +2,30 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/mock_fido_ble_connection.h" +#include "device/fido/ble/mock_fido_ble_connection.h" #include <utility> namespace device { -MockFidoBleConnection::MockFidoBleConnection(std::string device_address) - : FidoBleConnection(std::move(device_address)) {} +MockFidoBleConnection::MockFidoBleConnection(BluetoothAdapter* adapter, + std::string device_address) + : FidoBleConnection(adapter, std::move(device_address)) {} MockFidoBleConnection::~MockFidoBleConnection() = default; +void MockFidoBleConnection::Connect(ConnectionCallback callback) { + ConnectPtr(&callback); +} + void MockFidoBleConnection::ReadControlPointLength( ControlPointLengthCallback callback) { ReadControlPointLengthPtr(&callback); } -void MockFidoBleConnection::ReadServiceRevisions( - ServiceRevisionsCallback callback) { - ReadServiceRevisionsPtr(&callback); -} - void MockFidoBleConnection::WriteControlPoint(const std::vector<uint8_t>& data, WriteCallback callback) { WriteControlPointPtr(data, &callback); } -void MockFidoBleConnection::WriteServiceRevision( - ServiceRevision service_revision, - WriteCallback callback) { - WriteServiceRevisionPtr(service_revision, &callback); -} - } // namespace device diff --git a/chromium/device/fido/mock_fido_ble_connection.h b/chromium/device/fido/ble/mock_fido_ble_connection.h index a2db59fa0ed..775859b1cfe 100644 --- a/chromium/device/fido/mock_fido_ble_connection.h +++ b/chromium/device/fido/ble/mock_fido_ble_connection.h @@ -2,45 +2,38 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef DEVICE_FIDO_MOCK_FIDO_BLE_CONNECTION_H_ -#define DEVICE_FIDO_MOCK_FIDO_BLE_CONNECTION_H_ +#ifndef DEVICE_FIDO_BLE_MOCK_FIDO_BLE_CONNECTION_H_ +#define DEVICE_FIDO_BLE_MOCK_FIDO_BLE_CONNECTION_H_ #include <string> #include <vector> #include "base/component_export.h" #include "base/macros.h" -#include "device/fido/fido_ble_connection.h" +#include "device/fido/ble/fido_ble_connection.h" #include "testing/gmock/include/gmock/gmock.h" namespace device { +class BluetoothAdapter; + class MockFidoBleConnection : public FidoBleConnection { public: - explicit MockFidoBleConnection(std::string device_address); + MockFidoBleConnection(BluetoothAdapter* adapter, std::string device_address); ~MockFidoBleConnection() override; - MOCK_METHOD0(Connect, void()); // GMock cannot mock a method taking a move-only type. // TODO(https://crbug.com/729950): Remove these workarounds once support for // move-only types is added to GMock. + MOCK_METHOD1(ConnectPtr, void(ConnectionCallback* cb)); MOCK_METHOD1(ReadControlPointLengthPtr, void(ControlPointLengthCallback* cb)); - MOCK_METHOD1(ReadServiceRevisionsPtr, void(ServiceRevisionsCallback* cb)); MOCK_METHOD2(WriteControlPointPtr, void(const std::vector<uint8_t>& data, WriteCallback* cb)); - MOCK_METHOD2(WriteServiceRevisionPtr, - void(ServiceRevision service_revision, WriteCallback* cb)); + void Connect(ConnectionCallback cb) override; void ReadControlPointLength(ControlPointLengthCallback cb) override; - void ReadServiceRevisions(ServiceRevisionsCallback cb) override; void WriteControlPoint(const std::vector<uint8_t>& data, WriteCallback cb) override; - void WriteServiceRevision(ServiceRevision service_revision, - WriteCallback cb) override; - - ConnectionStatusCallback& connection_status_callback() { - return connection_status_callback_; - } ReadCallback& read_callback() { return read_callback_; } @@ -50,4 +43,4 @@ class MockFidoBleConnection : public FidoBleConnection { } // namespace device -#endif // DEVICE_FIDO_MOCK_FIDO_BLE_CONNECTION_H_ +#endif // DEVICE_FIDO_BLE_MOCK_FIDO_BLE_CONNECTION_H_ diff --git a/chromium/device/fido/ble_adapter_power_manager.cc b/chromium/device/fido/ble_adapter_power_manager.cc new file mode 100644 index 00000000000..7c7a8ec31e4 --- /dev/null +++ b/chromium/device/fido/ble_adapter_power_manager.cc @@ -0,0 +1,51 @@ +// Copyright 2018 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 "device/fido/ble_adapter_power_manager.h" + +#include <utility> + +#include "base/bind_helpers.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" + +namespace device { + +BleAdapterPowerManager::BleAdapterPowerManager( + FidoRequestHandlerBase* request_handler) + : request_handler_(request_handler), weak_factory_(this) { + BluetoothAdapterFactory::Get().GetAdapter(base::BindRepeating( + &BleAdapterPowerManager::Start, weak_factory_.GetWeakPtr())); +} + +BleAdapterPowerManager::~BleAdapterPowerManager() { + if (adapter_powered_on_programmatically_) + SetAdapterPower(false /* set_power_on */); + + if (adapter_) + adapter_->RemoveObserver(this); +} + +void BleAdapterPowerManager::SetAdapterPower(bool set_power_on) { + if (set_power_on) + adapter_powered_on_programmatically_ = true; + + adapter_->SetPowered(set_power_on, base::DoNothing(), base::DoNothing()); +} + +void BleAdapterPowerManager::AdapterPoweredChanged(BluetoothAdapter* adapter, + bool powered) { + request_handler_->OnBluetoothAdapterPowerChanged(powered); +} + +void BleAdapterPowerManager::Start(scoped_refptr<BluetoothAdapter> adapter) { + DCHECK(!adapter_); + adapter_ = std::move(adapter); + DCHECK(adapter_); + adapter_->AddObserver(this); + + request_handler_->OnBluetoothAdapterEnumerated( + adapter_->IsPresent(), adapter_->IsPowered(), adapter_->CanPower()); +} + +} // namespace device diff --git a/chromium/device/fido/ble_adapter_power_manager.h b/chromium/device/fido/ble_adapter_power_manager.h new file mode 100644 index 00000000000..194e0defbf7 --- /dev/null +++ b/chromium/device/fido/ble_adapter_power_manager.h @@ -0,0 +1,49 @@ +// Copyright 2018 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. + +#ifndef DEVICE_FIDO_BLE_ADAPTER_POWER_MANAGER_H_ +#define DEVICE_FIDO_BLE_ADAPTER_POWER_MANAGER_H_ + +#include "base/component_export.h" +#include "base/macros.h" +#include "base/memory/scoped_refptr.h" +#include "base/memory/weak_ptr.h" +#include "device/bluetooth/bluetooth_adapter.h" +#include "device/fido/fido_request_handler_base.h" + +namespace device { + +class COMPONENT_EXPORT(DEVICE_FIDO) BleAdapterPowerManager + : public BluetoothAdapter::Observer { + public: + // Handles notifying |request_handler| when BluetoothAdapter is powered on and + // off. Exposes API for |request_handler| to power on BluetoothAdapter + // programmatically, and if BluetoothAdapter was powered on programmatically, + // powers off BluetoothAdapter when |this| goes out of scope. + // |request_handler| must outlive |this|. + BleAdapterPowerManager(FidoRequestHandlerBase* request_handler); + ~BleAdapterPowerManager() override; + + void SetAdapterPower(bool set_power_on); + + private: + friend class FidoBleAdapterPowerManagerTest; + + // BluetoothAdapter::Observer: + void AdapterPoweredChanged(BluetoothAdapter* adapter, bool powered) override; + + void Start(scoped_refptr<BluetoothAdapter> adapter); + + FidoRequestHandlerBase* const request_handler_; + scoped_refptr<BluetoothAdapter> adapter_; + bool adapter_powered_on_programmatically_ = false; + + base::WeakPtrFactory<BleAdapterPowerManager> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(BleAdapterPowerManager); +}; + +} // namespace device + +#endif // DEVICE_FIDO_BLE_ADAPTER_POWER_MANAGER_H_ diff --git a/chromium/device/fido/ble_adapter_power_manager_unittest.cc b/chromium/device/fido/ble_adapter_power_manager_unittest.cc new file mode 100644 index 00000000000..8e62fc5a9d9 --- /dev/null +++ b/chromium/device/fido/ble_adapter_power_manager_unittest.cc @@ -0,0 +1,143 @@ +// Copyright 2018 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 "device/fido/ble_adapter_power_manager.h" + +#include <memory> + +#include "base/test/scoped_task_environment.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" +#include "device/bluetooth/test/mock_bluetooth_adapter.h" +#include "device/fido/fido_request_handler_base.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace device { + +namespace { + +using ::testing::_; + +class MockTransportAvailabilityObserver + : public FidoRequestHandlerBase::TransportAvailabilityObserver { + public: + MockTransportAvailabilityObserver() = default; + ~MockTransportAvailabilityObserver() override = default; + + MOCK_METHOD1(OnTransportAvailabilityEnumerated, + void(FidoRequestHandlerBase::TransportAvailabilityInfo data)); + MOCK_METHOD1(EmbedderControlsAuthenticatorDispatch, + bool(const FidoAuthenticator& authenticator)); + MOCK_METHOD1(BluetoothAdapterPowerChanged, void(bool is_powered_on)); + MOCK_METHOD1(FidoAuthenticatorAdded, + void(const FidoAuthenticator& authenticator)); + MOCK_METHOD1(FidoAuthenticatorRemoved, void(base::StringPiece device_id)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockTransportAvailabilityObserver); +}; + +class FakeFidoRequestHandlerBase : public FidoRequestHandlerBase { + public: + explicit FakeFidoRequestHandlerBase( + MockTransportAvailabilityObserver* observer) + : FidoRequestHandlerBase(nullptr, {}) { + set_observer(observer); + } + + private: + void DispatchRequest(FidoAuthenticator*) override {} + + DISALLOW_COPY_AND_ASSIGN(FakeFidoRequestHandlerBase); +}; + +} // namespace + +class FidoBleAdapterPowerManagerTest : public ::testing::Test { + public: + FidoBleAdapterPowerManagerTest() { + BluetoothAdapterFactory::SetAdapterForTesting(adapter_); + } + + std::unique_ptr<BleAdapterPowerManager> CreateTestBleAdapterPowerManager() { + return std::make_unique<BleAdapterPowerManager>( + fake_request_handler_.get()); + } + + MockBluetoothAdapter* adapter() { return adapter_.get(); } + MockTransportAvailabilityObserver* observer() { return mock_observer_.get(); } + bool adapter_powered_on_programmatically( + const BleAdapterPowerManager& adapter_power_manager) { + return adapter_power_manager.adapter_powered_on_programmatically_; + } + + protected: + base::test::ScopedTaskEnvironment scoped_task_environment_; + scoped_refptr<MockBluetoothAdapter> adapter_ = + base::MakeRefCounted<::testing::NiceMock<MockBluetoothAdapter>>(); + std::unique_ptr<MockTransportAvailabilityObserver> mock_observer_ = + std::make_unique<MockTransportAvailabilityObserver>(); + std::unique_ptr<FakeFidoRequestHandlerBase> fake_request_handler_ = + std::make_unique<FakeFidoRequestHandlerBase>(mock_observer_.get()); +}; + +TEST_F(FidoBleAdapterPowerManagerTest, AdapaterNotPresent) { + EXPECT_CALL(*adapter(), IsPresent()).WillOnce(::testing::Return(false)); + EXPECT_CALL(*adapter(), IsPowered()).WillOnce(::testing::Return(false)); + EXPECT_CALL(*adapter(), CanPower()).WillOnce(::testing::Return(false)); + + FidoRequestHandlerBase::TransportAvailabilityInfo data; + EXPECT_CALL(*observer(), OnTransportAvailabilityEnumerated(_)) + .WillOnce(::testing::SaveArg<0>(&data)); + + CreateTestBleAdapterPowerManager(); + scoped_task_environment_.RunUntilIdle(); + + EXPECT_FALSE(data.is_ble_powered); + EXPECT_FALSE(data.can_power_on_ble_adapter); +} + +TEST_F(FidoBleAdapterPowerManagerTest, AdapaterPresentAndPowered) { + EXPECT_CALL(*adapter(), IsPresent()).WillOnce(::testing::Return(true)); + EXPECT_CALL(*adapter(), IsPowered()).WillOnce(::testing::Return(true)); + EXPECT_CALL(*adapter(), CanPower()).WillOnce(::testing::Return(false)); + + FidoRequestHandlerBase::TransportAvailabilityInfo data; + EXPECT_CALL(*observer(), OnTransportAvailabilityEnumerated(_)) + .WillOnce(::testing::SaveArg<0>(&data)); + + CreateTestBleAdapterPowerManager(); + scoped_task_environment_.RunUntilIdle(); + + EXPECT_TRUE(data.is_ble_powered); + EXPECT_FALSE(data.can_power_on_ble_adapter); +} + +TEST_F(FidoBleAdapterPowerManagerTest, AdapaterPresentAndCanBePowered) { + EXPECT_CALL(*adapter(), IsPresent()).WillOnce(::testing::Return(true)); + EXPECT_CALL(*adapter(), IsPowered()).WillOnce(::testing::Return(false)); + EXPECT_CALL(*adapter(), CanPower()).WillOnce(::testing::Return(true)); + + FidoRequestHandlerBase::TransportAvailabilityInfo data; + EXPECT_CALL(*observer(), OnTransportAvailabilityEnumerated(_)) + .WillOnce(::testing::SaveArg<0>(&data)); + + CreateTestBleAdapterPowerManager(); + scoped_task_environment_.RunUntilIdle(); + + EXPECT_FALSE(data.is_ble_powered); + EXPECT_TRUE(data.can_power_on_ble_adapter); +} + +TEST_F(FidoBleAdapterPowerManagerTest, TestSetBluetoothPowerOn) { + auto power_manager = CreateTestBleAdapterPowerManager(); + ::testing::InSequence s; + EXPECT_CALL(*adapter(), SetPowered(true, _, _)); + EXPECT_CALL(*adapter(), SetPowered(false, _, _)); + power_manager->SetAdapterPower(true /* set_power_on */); + EXPECT_TRUE(adapter_powered_on_programmatically(*power_manager)); + power_manager.reset(); +} + +} // namespace device diff --git a/chromium/device/fido/cable/cable_discovery_data.h b/chromium/device/fido/cable/cable_discovery_data.h new file mode 100644 index 00000000000..67bfccef2ee --- /dev/null +++ b/chromium/device/fido/cable/cable_discovery_data.h @@ -0,0 +1,45 @@ +// Copyright 2018 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. + +#ifndef DEVICE_FIDO_CABLE_CABLE_DISCOVERY_DATA_H_ +#define DEVICE_FIDO_CABLE_CABLE_DISCOVERY_DATA_H_ + +#include <stdint.h> +#include <array> + +#include "base/component_export.h" + +namespace device { + +constexpr size_t kEphemeralIdSize = 16; +constexpr size_t kSessionPreKeySize = 32; + +using EidArray = std::array<uint8_t, kEphemeralIdSize>; +using SessionPreKeyArray = std::array<uint8_t, kSessionPreKeySize>; + +// Encapsulates information required to discover Cable device per single +// credential. When multiple credentials are enrolled to a single account +// (i.e. more than one phone has been enrolled to an user account as a +// security key), then FidoCableDiscovery must advertise for all of the client +// EID received from the relying party. +// TODO(hongjunchoi): Add discovery data required for MakeCredential request. +// See: https://crbug.com/837088 +struct COMPONENT_EXPORT(DEVICE_FIDO) CableDiscoveryData { + CableDiscoveryData(uint8_t version, + const EidArray& client_eid, + const EidArray& authenticator_eid, + const SessionPreKeyArray& session_pre_key); + CableDiscoveryData(const CableDiscoveryData& data); + CableDiscoveryData& operator=(const CableDiscoveryData& other); + ~CableDiscoveryData(); + + uint8_t version; + EidArray client_eid; + EidArray authenticator_eid; + SessionPreKeyArray session_pre_key; +}; + +} // namespace device + +#endif // DEVICE_FIDO_CABLE_CABLE_DISCOVERY_DATA_H_ diff --git a/chromium/device/fido/fido_cable_device.cc b/chromium/device/fido/cable/fido_cable_device.cc index 31cf975dfb0..1c5dd607dd0 100644 --- a/chromium/device/fido/fido_cable_device.cc +++ b/chromium/device/fido/cable/fido_cable_device.cc @@ -2,14 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fido_cable_device.h" +#include "device/fido/cable/fido_cable_device.h" #include <utility> #include "base/strings/string_piece.h" #include "base/threading/thread_task_runner_handle.h" -#include "device/fido/fido_ble_connection.h" -#include "device/fido/fido_ble_frames.h" +#include "device/fido/ble/fido_ble_connection.h" +#include "device/fido/ble/fido_ble_frames.h" #include "device/fido/fido_constants.h" #include "device/fido/fido_parsing_utils.h" @@ -113,8 +113,8 @@ FidoCableDevice::EncryptionData::~EncryptionData() = default; // FidoCableDevice::EncryptionData ---------------------------------------- -FidoCableDevice::FidoCableDevice(std::string address) - : FidoBleDevice(std::move(address)), weak_factory_(this) {} +FidoCableDevice::FidoCableDevice(BluetoothAdapter* adapter, std::string address) + : FidoBleDevice(adapter, std::move(address)), weak_factory_(this) {} FidoCableDevice::FidoCableDevice(std::unique_ptr<FidoBleConnection> connection) : FidoBleDevice(std::move(connection)), weak_factory_(this) {} @@ -128,9 +128,9 @@ void FidoCableDevice::DeviceTransact(std::vector<uint8_t> command, FROM_HERE, base::BindOnce(std::move(callback), base::nullopt)); state_ = State::kDeviceError; return; - } + } - ++encryption_data_->write_sequence_num; + ++encryption_data_->write_sequence_num; AddToPendingFrames(FidoBleDeviceCommand::kMsg, std::move(command), std::move(callback)); @@ -177,4 +177,8 @@ void FidoCableDevice::SetEncryptionData(std::string session_key, encryption_data_.emplace(std::move(session_key), nonce); } +FidoTransportProtocol FidoCableDevice::DeviceTransport() const { + return FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy; +} + } // namespace device diff --git a/chromium/device/fido/fido_cable_device.h b/chromium/device/fido/cable/fido_cable_device.h index 993df405760..29e3907f165 100644 --- a/chromium/device/fido/fido_cable_device.h +++ b/chromium/device/fido/cable/fido_cable_device.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef DEVICE_FIDO_FIDO_CABLE_DEVICE_H_ -#define DEVICE_FIDO_FIDO_CABLE_DEVICE_H_ +#ifndef DEVICE_FIDO_CABLE_FIDO_CABLE_DEVICE_H_ +#define DEVICE_FIDO_CABLE_FIDO_CABLE_DEVICE_H_ #include <array> #include <memory> @@ -16,12 +16,13 @@ #include "base/memory/weak_ptr.h" #include "base/optional.h" #include "crypto/aead.h" -#include "device/fido/fido_ble_device.h" +#include "device/fido/ble/fido_ble_device.h" namespace device { -class FidoBleFrame; +class BluetoothAdapter; class FidoBleConnection; +class FidoBleFrame; class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDevice : public FidoBleDevice { public: @@ -44,7 +45,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDevice : public FidoBleDevice { using FrameCallback = FidoBleTransaction::FrameCallback; - FidoCableDevice(std::string address); + FidoCableDevice(BluetoothAdapter* adapter, std::string address); // Constructor used for testing purposes. FidoCableDevice(std::unique_ptr<FidoBleConnection> connection); ~FidoCableDevice() override; @@ -61,6 +62,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDevice : public FidoBleDevice { void SetEncryptionData(std::string session_key, base::span<const uint8_t, 8> nonce); + FidoTransportProtocol DeviceTransport() const override; private: FRIEND_TEST_ALL_PREFIXES(FidoCableDeviceTest, @@ -76,4 +78,4 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDevice : public FidoBleDevice { } // namespace device -#endif // DEVICE_FIDO_FIDO_CABLE_DEVICE_H_ +#endif // DEVICE_FIDO_CABLE_FIDO_CABLE_DEVICE_H_ diff --git a/chromium/device/fido/fido_cable_device_unittest.cc b/chromium/device/fido/cable/fido_cable_device_unittest.cc index ec76ea0b098..e99e691a8d1 100644 --- a/chromium/device/fido/fido_cable_device_unittest.cc +++ b/chromium/device/fido/cable/fido_cable_device_unittest.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fido_cable_device.h" +#include "device/fido/cable/fido_cable_device.h" #include <array> #include <limits> @@ -16,8 +16,9 @@ #include "base/threading/sequenced_task_runner_handle.h" #include "crypto/aead.h" #include "device/bluetooth/test/bluetooth_test.h" +#include "device/bluetooth/test/mock_bluetooth_adapter.h" +#include "device/fido/ble/mock_fido_ble_connection.h" #include "device/fido/fido_parsing_utils.h" -#include "device/fido/mock_fido_ble_connection.h" #include "device/fido/test_callback_receiver.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -31,6 +32,7 @@ using ::testing::Invoke; using ::testing::Test; using TestDeviceCallbackReceiver = test::ValueCallbackReceiver<base::Optional<std::vector<uint8_t>>>; +using NiceMockBluetoothAdapter = ::testing::NiceMock<MockBluetoothAdapter>; // Sufficiently large test control point length as we are not interested // in testing fragmentations of BLE messages. All Cable messages are encrypted @@ -128,19 +130,16 @@ class FidoCableDeviceTest : public Test { public: FidoCableDeviceTest() { auto connection = std::make_unique<MockFidoBleConnection>( - BluetoothTestBase::kTestDeviceAddress1); + adapter_.get(), BluetoothTestBase::kTestDeviceAddress1); connection_ = connection.get(); device_ = std::make_unique<FidoCableDevice>(std::move(connection)); device_->SetEncryptionData(kTestSessionKey, kTestEncryptionNonce); - - connection_->connection_status_callback() = - device_->GetConnectionStatusCallbackForTesting(); connection_->read_callback() = device_->GetReadCallbackForTesting(); } void ConnectWithLength(uint16_t length) { - EXPECT_CALL(*connection(), Connect()).WillOnce(Invoke([this] { - connection()->connection_status_callback().Run(true); + EXPECT_CALL(*connection(), ConnectPtr).WillOnce(Invoke([](auto* callback) { + std::move(*callback).Run(true); })); EXPECT_CALL(*connection(), ReadControlPointLengthPtr(_)) @@ -157,6 +156,8 @@ class FidoCableDeviceTest : public Test { base::test::ScopedTaskEnvironment scoped_task_environment_; private: + scoped_refptr<MockBluetoothAdapter> adapter_ = + base::MakeRefCounted<NiceMockBluetoothAdapter>(); FakeCableAuthenticator authenticator_; MockFidoBleConnection* connection_; std::unique_ptr<FidoCableDevice> device_; diff --git a/chromium/device/fido/fido_cable_discovery.cc b/chromium/device/fido/cable/fido_cable_discovery.cc index 5911dda11f3..2934fa7248b 100644 --- a/chromium/device/fido/fido_cable_discovery.cc +++ b/chromium/device/fido/cable/fido_cable_discovery.cc @@ -2,12 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fido_cable_discovery.h" +#include "device/fido/cable/fido_cable_discovery.h" #include <algorithm> #include <memory> #include <utility> +#include "base/barrier_closure.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "base/callback_helpers.h" @@ -17,53 +18,15 @@ #include "device/bluetooth/bluetooth_advertisement.h" #include "device/bluetooth/bluetooth_discovery_session.h" #include "device/bluetooth/bluetooth_uuid.h" -#include "device/fido/fido_ble_uuids.h" -#include "device/fido/fido_cable_device.h" -#include "device/fido/fido_cable_handshake_handler.h" +#include "device/fido/ble/fido_ble_uuids.h" +#include "device/fido/cable/fido_cable_device.h" +#include "device/fido/cable/fido_cable_handshake_handler.h" #include "device/fido/fido_parsing_utils.h" namespace device { namespace { -#if defined(OS_MACOSX) - -// Convert byte array into GUID formatted string as defined by RFC 4122. -// As we are converting 128 bit UUID, |bytes| must be have length of 16. -// https://tools.ietf.org/html/rfc4122 -std::string ConvertBytesToUuid(base::span<const uint8_t, 16> bytes) { - uint64_t most_significant_bytes = 0; - for (size_t i = 0; i < sizeof(uint64_t); i++) { - most_significant_bytes |= base::strict_cast<uint64_t>(bytes[i]) - << 8 * (7 - i); - } - - uint64_t least_significant_bytes = 0; - for (size_t i = 0; i < sizeof(uint64_t); i++) { - least_significant_bytes |= base::strict_cast<uint64_t>(bytes[i + 8]) - << 8 * (7 - i); - } - - return base::StringPrintf( - "%08x-%04x-%04x-%04x-%012llx", - static_cast<unsigned int>(most_significant_bytes >> 32), - static_cast<unsigned int>((most_significant_bytes >> 16) & 0x0000ffff), - static_cast<unsigned int>(most_significant_bytes & 0x0000ffff), - static_cast<unsigned int>(least_significant_bytes >> 48), - least_significant_bytes & 0x0000ffff'ffffffffULL); -} - -#endif - -const BluetoothUUID& CableAdvertisementUUID() { - static const BluetoothUUID service_uuid(kCableAdvertisementUUID); - return service_uuid; -} - -bool IsCableDevice(const BluetoothDevice* device) { - return base::ContainsKey(device->GetServiceData(), CableAdvertisementUUID()); -} - // Construct advertisement data with different formats depending on client's // operating system. Ideally, we advertise EIDs as part of Service Data, but // this isn't available on all platforms. On Windows we use Manufacturer Data @@ -71,32 +34,48 @@ bool IsCableDevice(const BluetoothDevice* device) { // with the EID as its UUID. std::unique_ptr<BluetoothAdvertisement::Data> ConstructAdvertisementData( uint8_t version_number, - base::span<const uint8_t, FidoCableDiscovery::kEphemeralIdSize> - client_eid) { + base::span<const uint8_t, kEphemeralIdSize> client_eid) { auto advertisement_data = std::make_unique<BluetoothAdvertisement::Data>( BluetoothAdvertisement::AdvertisementType::ADVERTISEMENT_TYPE_BROADCAST); #if defined(OS_MACOSX) auto list = std::make_unique<BluetoothAdvertisement::UUIDList>(); - list->emplace_back(kCableAdvertisementUUID); - list->emplace_back(ConvertBytesToUuid(client_eid)); + list->emplace_back(kCableAdvertisementUUID16); + list->emplace_back(fido_parsing_utils::ConvertBytesToUuid(client_eid)); advertisement_data->set_service_uuids(std::move(list)); #elif defined(OS_WIN) - constexpr uint16_t kFidoManufacturerId = 0xFFFD; - constexpr std::array<uint8_t, 2> kFidoManufacturerDataHeader = {0x51, 0xFE}; + // References: + // https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers + // go/google-ble-manufacturer-data-format + static constexpr uint16_t kGoogleManufacturerId = 0x00E0; + static constexpr uint8_t kCableGoogleManufacturerDataType = 0x15; + + // Reference: + // https://github.com/arnar/fido-2-specs/blob/fido-client-to-authenticator-protocol.bs#L4314 + static constexpr uint8_t kCableFlags = 0x20; + + static constexpr uint8_t kCableGoogleManufacturerDataLength = + 3u + kEphemeralIdSize; + std::array<uint8_t, 4> kCableGoogleManufacturerDataHeader = { + kCableGoogleManufacturerDataLength, kCableGoogleManufacturerDataType, + kCableFlags, version_number}; auto manufacturer_data = std::make_unique<BluetoothAdvertisement::ManufacturerData>(); std::vector<uint8_t> manufacturer_data_value; fido_parsing_utils::Append(&manufacturer_data_value, - kFidoManufacturerDataHeader); + kCableGoogleManufacturerDataHeader); fido_parsing_utils::Append(&manufacturer_data_value, client_eid); - manufacturer_data->emplace(kFidoManufacturerId, + manufacturer_data->emplace(kGoogleManufacturerId, std::move(manufacturer_data_value)); advertisement_data->set_manufacturer_data(std::move(manufacturer_data)); #elif defined(OS_LINUX) || defined(OS_CHROMEOS) + // Reference: + // https://github.com/arnar/fido-2-specs/blob/fido-client-to-authenticator-protocol.bs#L4314 + static constexpr uint8_t kCableFlags = 0x20; + // Service data for ChromeOS and Linux is 1 byte corresponding to Cable flags, // followed by 1 byte corresponding to Cable version number, followed by 16 // bytes corresponding to client EID. @@ -104,11 +83,12 @@ std::unique_ptr<BluetoothAdvertisement::Data> ConstructAdvertisementData( std::vector<uint8_t> service_data_value(18, 0); // Since the remainder of this service data field is a Cable EID, set the 5th // bit of the flag byte. - service_data_value[0] = 1 << 5; + service_data_value[0] = kCableFlags; service_data_value[1] = version_number; std::copy(client_eid.begin(), client_eid.end(), service_data_value.begin() + 2); - service_data->emplace(kCableAdvertisementUUID, std::move(service_data_value)); + service_data->emplace(kCableAdvertisementUUID128, + std::move(service_data_value)); advertisement_data->set_service_data(std::move(service_data)); #endif @@ -117,9 +97,9 @@ std::unique_ptr<BluetoothAdvertisement::Data> ConstructAdvertisementData( } // namespace -// FidoCableDiscovery::CableDiscoveryData ------------------------------------- +// CableDiscoveryData ------------------------------------- -FidoCableDiscovery::CableDiscoveryData::CableDiscoveryData( +CableDiscoveryData::CableDiscoveryData( uint8_t version, const EidArray& client_eid, const EidArray& authenticator_eid, @@ -129,19 +109,30 @@ FidoCableDiscovery::CableDiscoveryData::CableDiscoveryData( authenticator_eid(authenticator_eid), session_pre_key(session_pre_key) {} -FidoCableDiscovery::CableDiscoveryData::CableDiscoveryData( - const CableDiscoveryData& data) = default; +CableDiscoveryData::CableDiscoveryData(const CableDiscoveryData& data) = + default; -FidoCableDiscovery::CableDiscoveryData& FidoCableDiscovery::CableDiscoveryData:: -operator=(const CableDiscoveryData& other) = default; +CableDiscoveryData& CableDiscoveryData::operator=( + const CableDiscoveryData& other) = default; -FidoCableDiscovery::CableDiscoveryData::~CableDiscoveryData() = default; +CableDiscoveryData::~CableDiscoveryData() = default; // FidoCableDiscovery --------------------------------------------------------- FidoCableDiscovery::FidoCableDiscovery( std::vector<CableDiscoveryData> discovery_data) - : discovery_data_(std::move(discovery_data)), weak_factory_(this) {} + : FidoBleDiscoveryBase( + FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy), + discovery_data_(std::move(discovery_data)), + weak_factory_(this) { +// Windows currently does not support multiple EIDs, thus we ignore any extra +// discovery data. +// TODO(https://crbug.com/837088): Add support for multiple EIDs on Windows. +#if defined(OS_WIN) + if (discovery_data_.size() > 1u) + discovery_data_.erase(discovery_data_.begin() + 1, discovery_data_.end()); +#endif +} // This is a workaround for https://crbug.com/846522 FidoCableDiscovery::~FidoCableDiscovery() { @@ -185,14 +176,49 @@ void FidoCableDiscovery::DeviceRemoved(BluetoothAdapter* adapter, } } +void FidoCableDiscovery::AdapterPoweredChanged(BluetoothAdapter* adapter, + bool powered) { + // If Bluetooth adapter is powered on, resume scanning for nearby Cable + // devices and start advertising client EIDs. + if (powered) { + StartCableDiscovery(); + } else { + // In order to prevent duplicate client EIDs from being advertised when + // BluetoothAdapter is powered back on, unregister all existing client + // EIDs. + StopAdvertisements(base::DoNothing()); + } +} + void FidoCableDiscovery::OnSetPowered() { DCHECK(adapter()); base::SequencedTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(&FidoCableDiscovery::StartAdvertisement, + FROM_HERE, base::BindOnce(&FidoCableDiscovery::StartCableDiscovery, weak_factory_.GetWeakPtr())); } +void FidoCableDiscovery::StartCableDiscovery() { + // Error callback OnStartDiscoverySessionError() is defined in the base class + // FidoBleDiscoveryBase. + adapter()->StartDiscoverySessionWithFilter( + std::make_unique<BluetoothDiscoveryFilter>( + BluetoothTransport::BLUETOOTH_TRANSPORT_LE), + base::AdaptCallbackForRepeating( + base::BindOnce(&FidoCableDiscovery::OnStartDiscoverySessionWithFilter, + weak_factory_.GetWeakPtr())), + base::AdaptCallbackForRepeating( + base::BindOnce(&FidoCableDiscovery::OnStartDiscoverySessionError, + weak_factory_.GetWeakPtr()))); +} + +void FidoCableDiscovery::OnStartDiscoverySessionWithFilter( + std::unique_ptr<BluetoothDiscoverySession> session) { + SetDiscoverySession(std::move(session)); + DVLOG(2) << "Discovery session started."; + StartAdvertisement(); +} + void FidoCableDiscovery::StartAdvertisement() { DCHECK(adapter()); @@ -208,6 +234,20 @@ void FidoCableDiscovery::StartAdvertisement() { } } +void FidoCableDiscovery::StopAdvertisements(base::OnceClosure callback) { + auto barrier_closure = + base::BarrierClosure(advertisement_success_counter_, std::move(callback)); + for (auto advertisement : advertisements_) + advertisement.second->Unregister(barrier_closure, base::DoNothing()); + +#if !defined(OS_WIN) + // On Windows the discovery is the only owner of the advertisements, meaning + // the advertisements would be destroyed before |barrier_closure| could be + // invoked. + advertisements_.clear(); +#endif // !defined(OS_WIN) +} + void FidoCableDiscovery::OnAdvertisementRegistered( const EidArray& client_eid, scoped_refptr<BluetoothAdvertisement> advertisement) { @@ -223,32 +263,16 @@ void FidoCableDiscovery::OnAdvertisementRegisterError( } void FidoCableDiscovery::RecordAdvertisementResult(bool is_success) { - is_success ? ++advertisement_success_counter_ - : ++advertisement_failure_counter_; - - // Wait until all advertisements are sent out. - if (advertisement_success_counter_ + advertisement_failure_counter_ != - discovery_data_.size()) { + // If at least one advertisement succeeds, then notify discovery start. + if (is_success) { + if (!advertisement_success_counter_++) + NotifyDiscoveryStarted(true); return; } - // No advertisements succeeded, no point in starting scanning. - if (!advertisement_success_counter_) { + // No advertisements succeeded, no point in continuing with Cable discovery. + if (++advertisement_failure_counter_ == discovery_data_.size()) NotifyDiscoveryStarted(false); - return; - } - - // At least one advertisement succeeded and all advertisement has been - // processed. Start scanning. - adapter()->StartDiscoverySessionWithFilter( - std::make_unique<BluetoothDiscoveryFilter>( - BluetoothTransport::BLUETOOTH_TRANSPORT_LE), - base::AdaptCallbackForRepeating( - base::BindOnce(&FidoCableDiscovery::OnStartDiscoverySessionWithFilter, - weak_factory_.GetWeakPtr())), - base::AdaptCallbackForRepeating( - base::BindOnce(&FidoCableDiscovery::OnStartDiscoverySessionError, - weak_factory_.GetWeakPtr()))); } void FidoCableDiscovery::CableDeviceFound(BluetoothAdapter* adapter, @@ -265,18 +289,22 @@ void FidoCableDiscovery::CableDeviceFound(BluetoothAdapter* adapter, if (!extract_success) return; - auto cable_device = std::make_unique<FidoCableDevice>(device->GetAddress()); - // At most one handshake messages should be exchanged for each Cable device. - if (!base::ContainsKey(cable_handshake_handlers_, cable_device->GetId())) { - ConductEncryptionHandshake(std::move(cable_device), - found_cable_device_data->session_pre_key, nonce); - } + auto cable_device = + std::make_unique<FidoCableDevice>(adapter, device->GetAddress()); + StopAdvertisements( + base::BindOnce(&FidoCableDiscovery::ConductEncryptionHandshake, + weak_factory_.GetWeakPtr(), std::move(cable_device), + found_cable_device_data->session_pre_key, nonce)); } void FidoCableDiscovery::ConductEncryptionHandshake( std::unique_ptr<FidoCableDevice> cable_device, base::span<const uint8_t, kSessionPreKeySize> session_pre_key, base::span<const uint8_t, 8> nonce) { + // At most one handshake messages should be exchanged for each Cable device. + if (base::ContainsKey(cable_handshake_handlers_, cable_device->GetId())) + return; + auto handshake_handler = CreateHandshakeHandler(cable_device.get(), session_pre_key, nonce); auto* const handshake_handler_ptr = handshake_handler.get(); @@ -296,18 +324,34 @@ void FidoCableDiscovery::ValidateAuthenticatorHandshakeMessage( if (!handshake_response) return; - if (!handshake_handler->ValidateAuthenticatorHandshakeMessage( - *handshake_response)) - return; + if (handshake_handler->ValidateAuthenticatorHandshakeMessage( + *handshake_response)) { + DVLOG(2) << "Authenticator handshake validated"; + AddDevice(std::move(cable_device)); + } else { + DVLOG(2) << "Authenticator handshake invalid"; + } +} + +const CableDiscoveryData* FidoCableDiscovery::GetFoundCableDiscoveryData( + const BluetoothDevice* device) const { + const auto* discovery_data = + GetFoundCableDiscoveryDataFromServiceData(device); + if (discovery_data != nullptr) { + return discovery_data; + } - AddDevice(std::move(cable_device)); + return GetFoundCableDiscoveryDataFromServiceUUIDs(device); } -const FidoCableDiscovery::CableDiscoveryData* -FidoCableDiscovery::GetFoundCableDiscoveryData( +const CableDiscoveryData* +FidoCableDiscovery::GetFoundCableDiscoveryDataFromServiceData( const BluetoothDevice* device) const { const auto* service_data = device->GetServiceDataForUUID(CableAdvertisementUUID()); + if (!service_data) { + return nullptr; + } DCHECK(service_data); // Received service data from authenticator must have a flag that signals that @@ -332,4 +376,26 @@ FidoCableDiscovery::GetFoundCableDiscoveryData( : nullptr; } +const CableDiscoveryData* +FidoCableDiscovery::GetFoundCableDiscoveryDataFromServiceUUIDs( + const BluetoothDevice* device) const { + const auto service_uuids = device->GetUUIDs(); + for (const auto& uuid : service_uuids) { + if (uuid == CableAdvertisementUUID()) + continue; + + auto discovery_data_iterator = std::find_if( + discovery_data_.begin(), discovery_data_.end(), + [&uuid](const auto& data) { + std::string received_eid_string = + fido_parsing_utils::ConvertBytesToUuid(data.authenticator_eid); + return uuid == BluetoothUUID(received_eid_string); + }); + if (discovery_data_iterator != discovery_data_.end()) { + return &(*discovery_data_iterator); + } + } + return nullptr; +} + } // namespace device diff --git a/chromium/device/fido/fido_cable_discovery.h b/chromium/device/fido/cable/fido_cable_discovery.h index a3b9ea27678..9bbad3b69f3 100644 --- a/chromium/device/fido/fido_cable_discovery.h +++ b/chromium/device/fido/cable/fido_cable_discovery.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef DEVICE_FIDO_FIDO_CABLE_DISCOVERY_H_ -#define DEVICE_FIDO_FIDO_CABLE_DISCOVERY_H_ +#ifndef DEVICE_FIDO_CABLE_FIDO_CABLE_DISCOVERY_H_ +#define DEVICE_FIDO_CABLE_FIDO_CABLE_DISCOVERY_H_ #include <stdint.h> @@ -18,7 +18,8 @@ #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" -#include "device/fido/fido_ble_discovery_base.h" +#include "device/fido/ble/fido_ble_discovery_base.h" +#include "device/fido/cable/cable_discovery_data.h" namespace device { @@ -30,33 +31,6 @@ class FidoCableHandshakeHandler; class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDiscovery : public FidoBleDiscoveryBase { public: - static constexpr size_t kEphemeralIdSize = 16; - static constexpr size_t kSessionPreKeySize = 32; - using EidArray = std::array<uint8_t, kEphemeralIdSize>; - using SessionPreKeyArray = std::array<uint8_t, kSessionPreKeySize>; - - // Encapsulates information required to discover Cable device per single - // credential. When multiple credentials are enrolled to a single account - // (i.e. more than one phone has been enrolled to an user account as a - // security key), then FidoCableDiscovery must advertise for all of the client - // EID received from the relying party. - // TODO(hongjunchoi): Add discovery data required for MakeCredential request. - // See: https://crbug.com/837088 - struct COMPONENT_EXPORT(DEVICE_FIDO) CableDiscoveryData { - CableDiscoveryData(uint8_t version, - const EidArray& client_eid, - const EidArray& authenticator_eid, - const SessionPreKeyArray& session_pre_key); - CableDiscoveryData(const CableDiscoveryData& data); - CableDiscoveryData& operator=(const CableDiscoveryData& other); - ~CableDiscoveryData(); - - uint8_t version; - EidArray client_eid; - EidArray authenticator_eid; - SessionPreKeyArray session_pre_key; - }; - FidoCableDiscovery(std::vector<CableDiscoveryData> discovery_data); ~FidoCableDiscovery() override; @@ -68,6 +42,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDiscovery private: FRIEND_TEST_ALL_PREFIXES(FidoCableDiscoveryTest, + TestDiscoveryWithAdvertisementFailures); + FRIEND_TEST_ALL_PREFIXES(FidoCableDiscoveryTest, TestUnregisterAdvertisementUponDestruction); // BluetoothAdapter::Observer: @@ -76,10 +52,14 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDiscovery BluetoothDevice* device) override; void DeviceRemoved(BluetoothAdapter* adapter, BluetoothDevice* device) override; + void AdapterPoweredChanged(BluetoothAdapter* adapter, bool powered) override; // FidoBleDiscoveryBase: void OnSetPowered() override; + void OnStartDiscoverySessionWithFilter( + std::unique_ptr<BluetoothDiscoverySession>) override; + void StartCableDiscovery(); void StartAdvertisement(); void OnAdvertisementRegistered( const EidArray& client_eid, @@ -91,6 +71,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDiscovery // invoke NotifyDiscoveryStarted(false). Otherwise kick off discovery session // once all advertisements has been processed. void RecordAdvertisementResult(bool is_success); + // Attempt to stop all on-going advertisements in best-effort basis. + // Once all the callbacks for Unregister() function is received, invoke + // |callback|. + void StopAdvertisements(base::OnceClosure callback); void CableDeviceFound(BluetoothAdapter* adapter, BluetoothDevice* device); void ConductEncryptionHandshake( std::unique_ptr<FidoCableDevice> device, @@ -103,6 +87,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDiscovery const CableDiscoveryData* GetFoundCableDiscoveryData( const BluetoothDevice* device) const; + const CableDiscoveryData* GetFoundCableDiscoveryDataFromServiceData( + const BluetoothDevice* device) const; + const CableDiscoveryData* GetFoundCableDiscoveryDataFromServiceUUIDs( + const BluetoothDevice* device) const; std::vector<CableDiscoveryData> discovery_data_; size_t advertisement_success_counter_ = 0; @@ -117,4 +105,4 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDiscovery } // namespace device -#endif // DEVICE_FIDO_FIDO_CABLE_DISCOVERY_H_ +#endif // DEVICE_FIDO_CABLE_FIDO_CABLE_DISCOVERY_H_ diff --git a/chromium/device/fido/fido_cable_discovery_unittest.cc b/chromium/device/fido/cable/fido_cable_discovery_unittest.cc index 859955de745..8af70961669 100644 --- a/chromium/device/fido/fido_cable_discovery_unittest.cc +++ b/chromium/device/fido/cable/fido_cable_discovery_unittest.cc @@ -2,28 +2,30 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fido_cable_discovery.h" +#include "device/fido/cable/fido_cable_discovery.h" #include <algorithm> #include <memory> #include <utility> #include "base/containers/span.h" +#include "base/run_loop.h" #include "base/stl_util.h" #include "build/build_config.h" #include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/bluetooth/bluetooth_advertisement.h" #include "device/bluetooth/test/bluetooth_test.h" #include "device/bluetooth/test/mock_bluetooth_adapter.h" -#include "device/fido/fido_ble_device.h" -#include "device/fido/fido_ble_uuids.h" -#include "device/fido/fido_cable_handshake_handler.h" +#include "device/fido/ble/fido_ble_device.h" +#include "device/fido/ble/fido_ble_uuids.h" +#include "device/fido/cable/fido_cable_handshake_handler.h" #include "device/fido/fido_parsing_utils.h" #include "device/fido/mock_fido_discovery_observer.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using ::testing::_; +using ::testing::Sequence; using ::testing::NiceMock; namespace device { @@ -34,41 +36,44 @@ constexpr uint8_t kTestCableVersionNumber = 0x01; // Constants required for discovering and constructing a Cable device that // are given by the relying party via an extension. -constexpr FidoCableDiscovery::EidArray kClientEid = { - {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, - 0x12, 0x13, 0x14, 0x15}}; +constexpr EidArray kClientEid = {{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, + 0x14, 0x15}}; constexpr char kUuidFormattedClientEid[] = "00010203-0405-0607-0809-101112131415"; -constexpr FidoCableDiscovery::EidArray kAuthenticatorEid = { - {0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, - 0x01, 0x01, 0x01, 0x01}}; +constexpr EidArray kAuthenticatorEid = {{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01}}; -constexpr FidoCableDiscovery::EidArray kInvalidAuthenticatorEid = { +constexpr EidArray kInvalidAuthenticatorEid = { {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; -constexpr FidoCableDiscovery::SessionPreKeyArray kTestSessionPreKey = { +constexpr SessionPreKeyArray kTestSessionPreKey = { {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}; -constexpr FidoCableDiscovery::EidArray kSecondaryClientEid = { - {0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, - 0x03, 0x02, 0x01, 0x00}}; +// TODO(https://crbug.com/837088): Add support for multiple EIDs on Windows. +#if !defined(OS_WIN) +constexpr EidArray kSecondaryClientEid = {{0x15, 0x14, 0x13, 0x12, 0x11, 0x10, + 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, + 0x03, 0x02, 0x01, 0x00}}; constexpr char kUuidFormattedSecondaryClientEid[] = "15141312-1110-0908-0706-050403020100"; -constexpr FidoCableDiscovery::EidArray kSecondaryAuthenticatorEid = { +constexpr EidArray kSecondaryAuthenticatorEid = { {0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee}}; -constexpr FidoCableDiscovery::SessionPreKeyArray kSecondarySessionPreKey = { +constexpr SessionPreKeyArray kSecondarySessionPreKey = { {0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd}}; +#endif // !defined(OS_WIN) // Below constants are used to construct MockBluetoothDevice for testing. constexpr char kTestBleDeviceAddress[] = "11:12:13:14:15:16"; @@ -81,6 +86,10 @@ std::unique_ptr<MockBluetoothDevice> CreateTestBluetoothDevice() { kTestBleDeviceAddress, true /* paired */, true /* connected */); } +ACTION_P(ReturnFromAsyncCall, closure) { + closure.Run(); +} + // Matcher to compare the content of advertisement data received from the // client. MATCHER_P2(IsAdvertisementContent, @@ -96,18 +105,24 @@ MATCHER_P2(IsAdvertisementContent, #elif defined(OS_WIN) const auto manufacturer_data = arg->manufacturer_data(); - const auto manufacturer_data_value = manufacturer_data->find(0xFFFD); + const auto manufacturer_data_value = manufacturer_data->find(0x00E0); if (manufacturer_data_value == manufacturer_data->end()) return false; - return fido_parsing_utils::ExtractSuffixSpan(manufacturer_data_value->second, - 2) == expected_client_eid; + const auto& manufacturer_data_payload = manufacturer_data_value->second; + return manufacturer_data_payload.size() >= 4u && + manufacturer_data_payload[0] == manufacturer_data_payload.size() - 1 && + manufacturer_data_payload[1] == 0x15 && // Manufacturer Data Type + manufacturer_data_payload[2] == 0x20 && // Cable Flags + manufacturer_data_payload[3] == kTestCableVersionNumber && + base::make_span(manufacturer_data_payload).subspan(4) == + expected_client_eid; #elif defined(OS_LINUX) || defined(OS_CHROMEOS) const auto service_data = arg->service_data(); const auto service_data_with_uuid = - service_data->find(kCableAdvertisementUUID); + service_data->find(kCableAdvertisementUUID128); if (service_data_with_uuid == service_data->end()) return false; @@ -148,8 +163,7 @@ class CableMockAdapter : public MockBluetoothAdapter { const AdvertisementErrorCallback&)); void AddNewTestBluetoothDevice( - base::span<const uint8_t, FidoCableDiscovery::kEphemeralIdSize> - authenticator_eid) { + base::span<const uint8_t, kEphemeralIdSize> authenticator_eid) { auto mock_device = CreateTestBluetoothDevice(); std::vector<uint8_t> service_data(18); @@ -157,7 +171,8 @@ class CableMockAdapter : public MockBluetoothAdapter { std::copy(authenticator_eid.begin(), authenticator_eid.end(), service_data.begin() + 2); BluetoothDevice::ServiceDataMap service_data_map; - service_data_map.emplace(kCableAdvertisementUUID, std::move(service_data)); + service_data_map.emplace(kCableAdvertisementUUID128, + std::move(service_data)); mock_device->UpdateAdvertisementData( 1 /* rssi */, base::nullopt /* flags */, BluetoothDevice::UUIDList(), @@ -171,35 +186,75 @@ class CableMockAdapter : public MockBluetoothAdapter { observer.DeviceAdded(this, mock_device_ptr); } + void AddNewTestAppleBluetoothDevice( + base::span<const uint8_t, kEphemeralIdSize> authenticator_eid) { + auto mock_device = CreateTestBluetoothDevice(); + // Apple doesn't allow advertising service data, so we advertise a 16 bit + // UUID plus the EID converted into 128 bit UUID. + mock_device->AddUUID(BluetoothUUID("fde2")); + mock_device->AddUUID(BluetoothUUID( + fido_parsing_utils::ConvertBytesToUuid(authenticator_eid))); + + auto* mock_device_ptr = mock_device.get(); + AddMockDevice(std::move(mock_device)); + + for (auto& observer : GetObservers()) + observer.DeviceAdded(this, mock_device_ptr); + } + void ExpectRegisterAdvertisementWithResponse( bool simulate_success, base::span<const uint8_t> expected_client_eid, base::StringPiece expected_uuid_formatted_client_eid, - scoped_refptr<CableMockBluetoothAdvertisement> advertisement_ptr = - nullptr) { - if (!advertisement_ptr) - advertisement_ptr = - base::MakeRefCounted<CableMockBluetoothAdvertisement>(); + Sequence sequence = Sequence(), + scoped_refptr<CableMockBluetoothAdvertisement> advertisement = nullptr) { + if (!advertisement) { + advertisement = base::MakeRefCounted<CableMockBluetoothAdvertisement>(); + EXPECT_CALL(*advertisement, Unregister(_, _)) + .WillRepeatedly(::testing::WithArg<0>( + [](const auto& callback) { callback.Run(); })); + } EXPECT_CALL(*this, RegisterAdvertisement( IsAdvertisementContent(expected_client_eid, expected_uuid_formatted_client_eid), _, _)) + .InSequence(sequence) .WillOnce(::testing::WithArgs<1, 2>( - [simulate_success, advertisement_ptr]( - const auto& success_callback, const auto& failure_callback) { + [simulate_success, advertisement](const auto& success_callback, + const auto& failure_callback) { simulate_success - ? success_callback.Run(advertisement_ptr) + ? success_callback.Run(advertisement) : failure_callback.Run(BluetoothAdvertisement::ErrorCode:: INVALID_ADVERTISEMENT_ERROR_CODE); })); } - void ExpectSuccessCallbackToSetPowered() { - EXPECT_CALL(*this, SetPowered(true, _, _)) + void ExpectSuccessCallbackToIsPowered() { + EXPECT_CALL(*this, IsPresent()).WillOnce(::testing::Return(true)); + EXPECT_CALL(*this, IsPowered()).WillOnce(::testing::Return(true)); + } + + void ExpectDiscoveryWithScanCallback() { + EXPECT_CALL(*this, StartDiscoverySessionWithFilterRaw(_, _, _)) + .WillOnce(::testing::WithArg<1>( + [this](const auto& callback) { callback.Run(nullptr); })); + } + + void ExpectDiscoveryWithScanCallback( + base::span<const uint8_t, kEphemeralIdSize> eid, + bool is_apple_device = false) { + EXPECT_CALL(*this, StartDiscoverySessionWithFilterRaw(_, _, _)) .WillOnce(::testing::WithArg<1>( - [](const auto& callback) { callback.Run(); })); + [this, eid, is_apple_device](const auto& callback) { + callback.Run(nullptr); + if (is_apple_device) { + AddNewTestAppleBluetoothDevice(eid); + } else { + AddNewTestBluetoothDevice(eid); + } + })); } protected: @@ -249,7 +304,7 @@ class FakeFidoCableDiscovery : public FidoCableDiscovery { class FidoCableDiscoveryTest : public ::testing::Test { public: std::unique_ptr<FidoCableDiscovery> CreateDiscovery() { - std::vector<FidoCableDiscovery::CableDiscoveryData> discovery_data; + std::vector<CableDiscoveryData> discovery_data; discovery_data.emplace_back(kTestCableVersionNumber, kClientEid, kAuthenticatorEid, kTestSessionPreKey); return std::make_unique<FakeFidoCableDiscovery>(std::move(discovery_data)); @@ -267,15 +322,29 @@ TEST_F(FidoCableDiscoveryTest, TestDiscoveryFindsNewDevice) { auto mock_adapter = base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>(); - ::testing::InSequence testing_sequence; - mock_adapter->ExpectSuccessCallbackToSetPowered(); + mock_adapter->ExpectSuccessCallbackToIsPowered(); + mock_adapter->ExpectDiscoveryWithScanCallback(kAuthenticatorEid); + mock_adapter->ExpectRegisterAdvertisementWithResponse( + true /* simulate_success */, kClientEid, kUuidFormattedClientEid); + + BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); + cable_discovery->Start(); + scoped_task_environment_.RunUntilIdle(); +} + +// Tests successful discovery flow for Apple Cable device. +TEST_F(FidoCableDiscoveryTest, TestDiscoveryFindsNewAppleDevice) { + auto cable_discovery = CreateDiscovery(); + NiceMock<MockFidoDiscoveryObserver> mock_observer; + EXPECT_CALL(mock_observer, DeviceAdded(_, _)); + cable_discovery->set_observer(&mock_observer); + + auto mock_adapter = + base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>(); + mock_adapter->ExpectSuccessCallbackToIsPowered(); + mock_adapter->ExpectDiscoveryWithScanCallback(kAuthenticatorEid, true); mock_adapter->ExpectRegisterAdvertisementWithResponse( true /* simulate_success */, kClientEid, kUuidFormattedClientEid); - EXPECT_CALL(*mock_adapter, StartDiscoverySessionWithFilterRaw(_, _, _)) - .WillOnce(::testing::WithArg<1>([&mock_adapter](const auto& callback) { - mock_adapter->AddNewTestBluetoothDevice(kAuthenticatorEid); - callback.Run(nullptr); - })); BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); cable_discovery->Start(); @@ -293,26 +362,26 @@ TEST_F(FidoCableDiscoveryTest, TestDiscoveryFindsIncorrectDevice) { auto mock_adapter = base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>(); - ::testing::InSequence testing_sequence; - mock_adapter->ExpectSuccessCallbackToSetPowered(); + mock_adapter->ExpectSuccessCallbackToIsPowered(); mock_adapter->ExpectRegisterAdvertisementWithResponse( true /* simulate_success */, kClientEid, kUuidFormattedClientEid); - EXPECT_CALL(*mock_adapter, StartDiscoverySessionWithFilterRaw(_, _, _)) - .WillOnce(::testing::WithArg<1>([&mock_adapter](const auto& callback) { - mock_adapter->AddNewTestBluetoothDevice(kInvalidAuthenticatorEid); - callback.Run(nullptr); - })); + mock_adapter->ExpectDiscoveryWithScanCallback(kInvalidAuthenticatorEid); BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); cable_discovery->Start(); scoped_task_environment_.RunUntilIdle(); } +// Windows currently does not support multiple EIDs, so the following tests are +// not applicable. +// TODO(https://crbug.com/837088): Support multiple EIDs on Windows and enable +// these tests. +#if !defined(OS_WIN) // Tests Cable discovery flow when multiple(2) sets of client/authenticator EIDs // are passed on from the relying party. We should expect 2 invocations of // BluetoothAdapter::RegisterAdvertisement(). TEST_F(FidoCableDiscoveryTest, TestDiscoveryWithMultipleEids) { - std::vector<FidoCableDiscovery::CableDiscoveryData> discovery_data; + std::vector<CableDiscoveryData> discovery_data; discovery_data.emplace_back(kTestCableVersionNumber, kClientEid, kAuthenticatorEid, kTestSessionPreKey); discovery_data.emplace_back(kTestCableVersionNumber, kSecondaryClientEid, @@ -320,24 +389,22 @@ TEST_F(FidoCableDiscoveryTest, TestDiscoveryWithMultipleEids) { kSecondarySessionPreKey); auto cable_discovery = std::make_unique<FakeFidoCableDiscovery>(std::move(discovery_data)); + auto mock_adapter = + base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>(); + mock_adapter->ExpectSuccessCallbackToIsPowered(); + mock_adapter->ExpectDiscoveryWithScanCallback(kAuthenticatorEid); + NiceMock<MockFidoDiscoveryObserver> mock_observer; EXPECT_CALL(mock_observer, DeviceAdded(_, _)); cable_discovery->set_observer(&mock_observer); - auto mock_adapter = - base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>(); - ::testing::InSequence testing_sequence; - mock_adapter->ExpectSuccessCallbackToSetPowered(); + Sequence sequence; mock_adapter->ExpectRegisterAdvertisementWithResponse( - true /* simulate_success */, kClientEid, kUuidFormattedClientEid); + true /* simulate_success */, kClientEid, kUuidFormattedClientEid, + sequence); mock_adapter->ExpectRegisterAdvertisementWithResponse( true /* simulate_success */, kSecondaryClientEid, - kUuidFormattedSecondaryClientEid); - EXPECT_CALL(*mock_adapter, StartDiscoverySessionWithFilterRaw(_, _, _)) - .WillOnce(::testing::WithArg<1>([&mock_adapter](const auto& callback) { - mock_adapter->AddNewTestBluetoothDevice(kAuthenticatorEid); - callback.Run(nullptr); - })); + kUuidFormattedSecondaryClientEid, sequence); BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); cable_discovery->Start(); @@ -348,7 +415,7 @@ TEST_F(FidoCableDiscoveryTest, TestDiscoveryWithMultipleEids) { // successfully. Since at least one advertisement are successfully processed, // scanning process should be invoked. TEST_F(FidoCableDiscoveryTest, TestDiscoveryWithPartialAdvertisementSuccess) { - std::vector<FidoCableDiscovery::CableDiscoveryData> discovery_data; + std::vector<CableDiscoveryData> discovery_data; discovery_data.emplace_back(kTestCableVersionNumber, kClientEid, kAuthenticatorEid, kTestSessionPreKey); discovery_data.emplace_back(kTestCableVersionNumber, kSecondaryClientEid, @@ -362,18 +429,15 @@ TEST_F(FidoCableDiscoveryTest, TestDiscoveryWithPartialAdvertisementSuccess) { auto mock_adapter = base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>(); - ::testing::InSequence testing_sequence; - mock_adapter->ExpectSuccessCallbackToSetPowered(); + mock_adapter->ExpectSuccessCallbackToIsPowered(); + Sequence sequence; mock_adapter->ExpectRegisterAdvertisementWithResponse( - true /* simulate_success */, kClientEid, kUuidFormattedClientEid); + true /* simulate_success */, kClientEid, kUuidFormattedClientEid, + sequence); mock_adapter->ExpectRegisterAdvertisementWithResponse( false /* simulate_success */, kSecondaryClientEid, - kUuidFormattedSecondaryClientEid); - EXPECT_CALL(*mock_adapter, StartDiscoverySessionWithFilterRaw(_, _, _)) - .WillOnce(::testing::WithArg<1>([&mock_adapter](const auto& callback) { - mock_adapter->AddNewTestBluetoothDevice(kAuthenticatorEid); - callback.Run(nullptr); - })); + kUuidFormattedSecondaryClientEid, sequence); + mock_adapter->ExpectDiscoveryWithScanCallback(kAuthenticatorEid); BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); cable_discovery->Start(); @@ -382,7 +446,7 @@ TEST_F(FidoCableDiscoveryTest, TestDiscoveryWithPartialAdvertisementSuccess) { // Test the scenario when all advertisement for client EID's fails. TEST_F(FidoCableDiscoveryTest, TestDiscoveryWithAdvertisementFailures) { - std::vector<FidoCableDiscovery::CableDiscoveryData> discovery_data; + std::vector<CableDiscoveryData> discovery_data; discovery_data.emplace_back(kTestCableVersionNumber, kClientEid, kAuthenticatorEid, kTestSessionPreKey); discovery_data.emplace_back(kTestCableVersionNumber, kSecondaryClientEid, @@ -397,20 +461,22 @@ TEST_F(FidoCableDiscoveryTest, TestDiscoveryWithAdvertisementFailures) { auto mock_adapter = base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>(); - ::testing::InSequence testing_sequence; - mock_adapter->ExpectSuccessCallbackToSetPowered(); + Sequence sequence; + mock_adapter->ExpectSuccessCallbackToIsPowered(); mock_adapter->ExpectRegisterAdvertisementWithResponse( - false /* simulate_success */, kClientEid, kUuidFormattedClientEid); + false /* simulate_success */, kClientEid, kUuidFormattedClientEid, + sequence); mock_adapter->ExpectRegisterAdvertisementWithResponse( false /* simulate_success */, kSecondaryClientEid, - kUuidFormattedSecondaryClientEid); - EXPECT_CALL(*mock_adapter, StartDiscoverySessionWithFilterRaw(_, _, _)) - .Times(0); + kUuidFormattedSecondaryClientEid, sequence); + mock_adapter->ExpectDiscoveryWithScanCallback(); BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); cable_discovery->Start(); scoped_task_environment_.RunUntilIdle(); + EXPECT_TRUE(cable_discovery->advertisements_.empty()); } +#endif // !defined(OS_WIN) TEST_F(FidoCableDiscoveryTest, TestUnregisterAdvertisementUponDestruction) { auto cable_discovery = CreateDiscovery(); @@ -418,13 +484,13 @@ TEST_F(FidoCableDiscoveryTest, TestUnregisterAdvertisementUponDestruction) { new CableMockBluetoothAdvertisement(); EXPECT_CALL(*advertisement, Unregister(_, _)).Times(1); - ::testing::InSequence testing_sequence; auto mock_adapter = base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>(); - mock_adapter->ExpectSuccessCallbackToSetPowered(); + mock_adapter->ExpectSuccessCallbackToIsPowered(); + mock_adapter->ExpectDiscoveryWithScanCallback(); mock_adapter->ExpectRegisterAdvertisementWithResponse( true /* simulate_success */, kClientEid, kUuidFormattedClientEid, - base::WrapRefCounted(advertisement)); + Sequence(), base::WrapRefCounted(advertisement)); BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); cable_discovery->Start(); @@ -434,4 +500,39 @@ TEST_F(FidoCableDiscoveryTest, TestUnregisterAdvertisementUponDestruction) { cable_discovery.reset(); } +// Tests that cable discovery resumes after Bluetooth adapter is powered on. +TEST_F(FidoCableDiscoveryTest, TestResumeDiscoveryAfterPoweredOn) { + auto cable_discovery = CreateDiscovery(); + NiceMock<MockFidoDiscoveryObserver> mock_observer; + EXPECT_CALL(mock_observer, DeviceAdded); + cable_discovery->set_observer(&mock_observer); + + auto mock_adapter = + base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>(); + EXPECT_CALL(*mock_adapter, IsPresent()).WillOnce(::testing::Return(true)); + + // After BluetoothAdapter is powered on, we expect that Cable discovery starts + // again. + mock_adapter->ExpectDiscoveryWithScanCallback(kAuthenticatorEid); + mock_adapter->ExpectRegisterAdvertisementWithResponse( + true /* simulate_success */, kClientEid, kUuidFormattedClientEid); + + // Wait until error callback for SetPowered() is invoked. Then, simulate + // Bluetooth adapter power change by invoking + // MockBluetoothAdapter::NotifyAdapterPoweredChanged(). + { + base::RunLoop run_loop; + auto quit = run_loop.QuitClosure(); + EXPECT_CALL(*mock_adapter, IsPowered) + .WillOnce(::testing::DoAll(ReturnFromAsyncCall(quit), + ::testing::Return(false))); + + BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter); + cable_discovery->Start(); + run_loop.Run(); + } + + mock_adapter->NotifyAdapterPoweredChanged(true); +} + } // namespace device diff --git a/chromium/device/fido/fido_cable_handshake_handler.cc b/chromium/device/fido/cable/fido_cable_handshake_handler.cc index a47c5d0f7cb..a227377a096 100644 --- a/chromium/device/fido/fido_cable_handshake_handler.cc +++ b/chromium/device/fido/cable/fido_cable_handshake_handler.cc @@ -2,11 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fido_cable_handshake_handler.h" +#include "device/fido/cable/fido_cable_handshake_handler.h" #include <algorithm> #include <utility> +#include "base/containers/span.h" #include "base/threading/thread_task_runner_handle.h" #include "components/cbor/cbor_reader.h" #include "components/cbor/cbor_values.h" @@ -14,7 +15,7 @@ #include "crypto/hkdf.h" #include "crypto/hmac.h" #include "crypto/random.h" -#include "device/fido/fido_cable_device.h" +#include "device/fido/cable/fido_cable_device.h" #include "device/fido/fido_constants.h" #include "device/fido/fido_parsing_utils.h" @@ -149,8 +150,8 @@ bool FidoCableHandshakeHandler::ValidateAuthenticatorHandshakeMessage( } cable_device_->SetEncryptionData( - GetEncryptionKeyAfterSuccessfulHandshake( - authenticator_random_nonce->second.GetBytestring()), + GetEncryptionKeyAfterSuccessfulHandshake(base::make_span<16>( + authenticator_random_nonce->second.GetBytestring())), nonce_); return true; diff --git a/chromium/device/fido/fido_cable_handshake_handler.h b/chromium/device/fido/cable/fido_cable_handshake_handler.h index fd764e53a30..175b55ddabb 100644 --- a/chromium/device/fido/fido_cable_handshake_handler.h +++ b/chromium/device/fido/cable/fido_cable_handshake_handler.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef DEVICE_FIDO_FIDO_CABLE_HANDSHAKE_HANDLER_H_ -#define DEVICE_FIDO_FIDO_CABLE_HANDSHAKE_HANDLER_H_ +#ifndef DEVICE_FIDO_CABLE_FIDO_CABLE_HANDSHAKE_HANDLER_H_ +#define DEVICE_FIDO_CABLE_FIDO_CABLE_HANDSHAKE_HANDLER_H_ #include <stdint.h> @@ -59,4 +59,4 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableHandshakeHandler { } // namespace device -#endif // DEVICE_FIDO_FIDO_CABLE_HANDSHAKE_HANDLER_H_ +#endif // DEVICE_FIDO_CABLE_FIDO_CABLE_HANDSHAKE_HANDLER_H_ diff --git a/chromium/device/fido/fido_cable_handshake_handler_fuzzer.cc b/chromium/device/fido/cable/fido_cable_handshake_handler_fuzzer.cc index 64a9a7afd74..5b0432e0d1d 100644 --- a/chromium/device/fido/fido_cable_handshake_handler_fuzzer.cc +++ b/chromium/device/fido/cable/fido_cable_handshake_handler_fuzzer.cc @@ -8,9 +8,13 @@ #include <array> #include "base/containers/span.h" -#include "device/fido/fido_cable_device.h" -#include "device/fido/fido_cable_handshake_handler.h" +#include "base/memory/ref_counted.h" +#include "device/bluetooth/test/mock_bluetooth_adapter.h" +#include "device/fido/cable/fido_cable_device.h" +#include "device/fido/cable/fido_cable_handshake_handler.h" #include "device/fido/fido_constants.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" namespace { @@ -30,7 +34,9 @@ constexpr char kTestDeviceAddress[] = "Fake_Address"; extern "C" int LLVMFuzzerTestOneInput(const uint8_t* raw_data, size_t size) { auto data_span = base::make_span(raw_data, size); - device::FidoCableDevice test_cable_device(kTestDeviceAddress); + auto adapter = + base::MakeRefCounted<::testing::NiceMock<device::MockBluetoothAdapter>>(); + device::FidoCableDevice test_cable_device(adapter.get(), kTestDeviceAddress); device::FidoCableHandshakeHandler handshake_handler( &test_cable_device, kTestNonce, kTestSessionPreKey); handshake_handler.ValidateAuthenticatorHandshakeMessage(data_span); diff --git a/chromium/device/fido/fido_cable_handshake_handler_unittest.cc b/chromium/device/fido/cable/fido_cable_handshake_handler_unittest.cc index 554707d1e9a..49ea55d3169 100644 --- a/chromium/device/fido/fido_cable_handshake_handler_unittest.cc +++ b/chromium/device/fido/cable/fido_cable_handshake_handler_unittest.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fido_cable_handshake_handler.h" +#include "device/fido/cable/fido_cable_handshake_handler.h" #include <array> #include <limits> @@ -19,11 +19,12 @@ #include "crypto/hkdf.h" #include "crypto/hmac.h" #include "device/bluetooth/test/bluetooth_test.h" -#include "device/fido/fido_ble_frames.h" -#include "device/fido/fido_cable_device.h" +#include "device/bluetooth/test/mock_bluetooth_adapter.h" +#include "device/fido/ble/fido_ble_frames.h" +#include "device/fido/ble/mock_fido_ble_connection.h" +#include "device/fido/cable/fido_cable_device.h" #include "device/fido/fido_constants.h" #include "device/fido/fido_parsing_utils.h" -#include "device/fido/mock_fido_ble_connection.h" #include "device/fido/test_callback_receiver.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -37,6 +38,7 @@ using ::testing::Invoke; using ::testing::Test; using TestDeviceCallbackReceiver = test::ValueCallbackReceiver<base::Optional<std::vector<uint8_t>>>; +using NiceMockBluetoothAdapter = ::testing::NiceMock<MockBluetoothAdapter>; // Sufficiently large test control point length as we are not interested // in testing fragmentations of BLE messages. All Cable messages are encrypted @@ -254,12 +256,10 @@ class FidoCableHandshakeHandlerTest : public Test { public: FidoCableHandshakeHandlerTest() { auto connection = std::make_unique<MockFidoBleConnection>( - BluetoothTestBase::kTestDeviceAddress1); + adapter_.get(), BluetoothTestBase::kTestDeviceAddress1); connection_ = connection.get(); device_ = std::make_unique<FidoCableDevice>(std::move(connection)); - connection_->connection_status_callback() = - device_->GetConnectionStatusCallbackForTesting(); connection_->read_callback() = device_->GetReadCallbackForTesting(); } @@ -271,8 +271,8 @@ class FidoCableHandshakeHandlerTest : public Test { } void ConnectWithLength(uint16_t length) { - EXPECT_CALL(*connection(), Connect()).WillOnce(Invoke([this] { - connection()->connection_status_callback().Run(true); + EXPECT_CALL(*connection(), ConnectPtr).WillOnce(Invoke([](auto* callback) { + std::move(*callback).Run(true); })); EXPECT_CALL(*connection(), ReadControlPointLengthPtr(_)) @@ -290,6 +290,8 @@ class FidoCableHandshakeHandlerTest : public Test { base::test::ScopedTaskEnvironment scoped_task_environment_; private: + scoped_refptr<MockBluetoothAdapter> adapter_ = + base::MakeRefCounted<NiceMockBluetoothAdapter>(); FakeCableAuthenticator authenticator_; MockFidoBleConnection* connection_; std::unique_ptr<FidoCableDevice> device_; diff --git a/chromium/device/fido/ctap_get_assertion_request.cc b/chromium/device/fido/ctap_get_assertion_request.cc index ac7a8282870..64285cb5565 100644 --- a/chromium/device/fido/ctap_get_assertion_request.cc +++ b/chromium/device/fido/ctap_get_assertion_request.cc @@ -146,7 +146,7 @@ CtapGetAssertionRequest& CtapGetAssertionRequest::SetPinProtocol( } CtapGetAssertionRequest& CtapGetAssertionRequest::SetCableExtension( - std::vector<FidoCableDiscovery::CableDiscoveryData> cable_extension) { + std::vector<CableDiscoveryData> cable_extension) { cable_extension_ = std::move(cable_extension); return *this; } diff --git a/chromium/device/fido/ctap_get_assertion_request.h b/chromium/device/fido/ctap_get_assertion_request.h index f9eafd70932..4264606d9bb 100644 --- a/chromium/device/fido/ctap_get_assertion_request.h +++ b/chromium/device/fido/ctap_get_assertion_request.h @@ -14,7 +14,7 @@ #include "base/containers/span.h" #include "base/macros.h" #include "base/optional.h" -#include "device/fido/fido_cable_discovery.h" +#include "device/fido/cable/cable_discovery_data.h" #include "device/fido/fido_constants.h" #include "device/fido/public_key_credential_descriptor.h" @@ -47,7 +47,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) CtapGetAssertionRequest { CtapGetAssertionRequest& SetPinAuth(std::vector<uint8_t> pin_auth); CtapGetAssertionRequest& SetPinProtocol(uint8_t pin_protocol); CtapGetAssertionRequest& SetCableExtension( - std::vector<FidoCableDiscovery::CableDiscoveryData> cable_extension); + std::vector<CableDiscoveryData> cable_extension); CtapGetAssertionRequest& SetAlternativeApplicationParameter( base::span<const uint8_t, kRpIdHashLength> alternative_application_parameter); @@ -77,8 +77,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) CtapGetAssertionRequest { } const base::Optional<uint8_t>& pin_protocol() const { return pin_protocol_; } - const base::Optional<std::vector<FidoCableDiscovery::CableDiscoveryData>>& - cable_extension() const { + const base::Optional<std::vector<CableDiscoveryData>>& cable_extension() + const { return cable_extension_; } const base::Optional<std::array<uint8_t, kRpIdHashLength>>& @@ -96,8 +96,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) CtapGetAssertionRequest { base::Optional<std::vector<PublicKeyCredentialDescriptor>> allow_list_; base::Optional<std::vector<uint8_t>> pin_auth_; base::Optional<uint8_t> pin_protocol_; - base::Optional<std::vector<FidoCableDiscovery::CableDiscoveryData>> - cable_extension_; + base::Optional<std::vector<CableDiscoveryData>> cable_extension_; base::Optional<std::array<uint8_t, kRpIdHashLength>> alternative_application_parameter_; }; diff --git a/chromium/device/fido/ctap_request_unittest.cc b/chromium/device/fido/ctap_request_unittest.cc index 30850b1a96f..2599eca7c9b 100644 --- a/chromium/device/fido/ctap_request_unittest.cc +++ b/chromium/device/fido/ctap_request_unittest.cc @@ -63,7 +63,8 @@ TEST(CTAPRequestTest, TestConstructGetAssertionRequest) { auto serialized_data = get_assertion_req.EncodeAsCBOR(); EXPECT_THAT(serialized_data, - ::testing::ElementsAreArray(test_data::kCtapGetAssertionRequest)); + ::testing::ElementsAreArray( + test_data::kTestComplexCtapGetAssertionRequest)); } TEST(CTAPRequestTest, TestConstructCtapAuthenticatorRequestParam) { @@ -127,7 +128,8 @@ TEST(CTAPRequestTest, ParseGetAssertionRequestForVirtualCtapKey) { 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03}; const auto request = ParseCtapGetAssertionRequest( - base::make_span(test_data::kCtapGetAssertionRequest).subspan(1)); + base::make_span(test_data::kTestComplexCtapGetAssertionRequest) + .subspan(1)); ASSERT_TRUE(request); EXPECT_THAT(request->client_data_hash(), ::testing::ElementsAreArray(test_data::kClientDataHash)); diff --git a/chromium/device/fido/ctap_response_unittest.cc b/chromium/device/fido/ctap_response_unittest.cc index 7b20e1e30a2..fa8b455c252 100644 --- a/chromium/device/fido/ctap_response_unittest.cc +++ b/chromium/device/fido/ctap_response_unittest.cc @@ -544,7 +544,7 @@ TEST(CTAPResponseTest, TestParseU2fSignWithNullCorruptedSignature) { TEST(CTAPResponseTest, TestReadGetInfoResponse) { auto get_info_response = - ReadCTAPGetInfoResponse(test_data::kTestAuthenticatorGetInfoResponse); + ReadCTAPGetInfoResponse(test_data::kTestGetInfoResponsePlatformDevice); ASSERT_TRUE(get_info_response); ASSERT_TRUE(get_info_response->max_msg_size()); EXPECT_EQ(*get_info_response->max_msg_size(), 1200u); @@ -574,8 +574,7 @@ TEST(CTAPResponseTest, TestReadGetInfoResponseWithIncorrectFormat) { TEST(CTAPResponseTest, TestSerializeGetInfoResponse) { AuthenticatorGetInfoResponse response( - {ProtocolVersion::kCtap, ProtocolVersion::kU2f}, - fido_parsing_utils::Materialize(kTestDeviceAaguid)); + {ProtocolVersion::kCtap, ProtocolVersion::kU2f}, kTestDeviceAaguid); response.SetExtensions({"uvm", "hmac-secret"}); AuthenticatorSupportedOptions options; options.SetSupportsResidentKey(true); @@ -592,7 +591,7 @@ TEST(CTAPResponseTest, TestSerializeGetInfoResponse) { EXPECT_THAT(EncodeToCBOR(response), ::testing::ElementsAreArray( - base::make_span(test_data::kTestAuthenticatorGetInfoResponse) + base::make_span(test_data::kTestGetInfoResponsePlatformDevice) .subspan(1))); } diff --git a/chromium/device/fido/device_operation.h b/chromium/device/fido/device_operation.h index 79b907b6554..757996323a4 100644 --- a/chromium/device/fido/device_operation.h +++ b/chromium/device/fido/device_operation.h @@ -10,9 +10,11 @@ #include <utility> #include <vector> +#include "base/bind.h" #include "base/callback.h" #include "base/macros.h" #include "base/optional.h" +#include "base/threading/sequenced_task_runner_handle.h" #include "device/fido/fido_constants.h" #include "device/fido/fido_device.h" @@ -39,8 +41,9 @@ class DeviceOperation { // TODO(hongjunchoi): Refactor so that |command| is never base::nullopt. void DispatchDeviceRequest(base::Optional<std::vector<uint8_t>> command, FidoDevice::DeviceCallback callback) { - if (!command) { - std::move(callback).Run(base::nullopt); + if (!command || device_->state() == FidoDevice::State::kDeviceError) { + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(std::move(callback), base::nullopt)); return; } diff --git a/chromium/device/fido/device_response_converter.cc b/chromium/device/fido/device_response_converter.cc index 45e77197690..e4ee349100d 100644 --- a/chromium/device/fido/device_response_converter.cc +++ b/chromium/device/fido/device_response_converter.cc @@ -8,6 +8,7 @@ #include <string> #include <utility> +#include "base/containers/span.h" #include "base/numerics/safe_conversions.h" #include "base/optional.h" #include "base/stl_util.h" @@ -169,8 +170,8 @@ base::Optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse( auto protocol = ConvertStringToProtocolVersion(version.GetString()); if (protocol == ProtocolVersion::kUnknown) { - DLOG(ERROR) << "Unexpected protocol version received."; - return base::nullopt; + VLOG(2) << "Unexpected protocol version received."; + continue; } if (!protocol_versions.insert(protocol).second) @@ -186,8 +187,9 @@ base::Optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse( return base::nullopt; } - AuthenticatorGetInfoResponse response(std::move(protocol_versions), - it->second.GetBytestring()); + AuthenticatorGetInfoResponse response( + std::move(protocol_versions), + base::make_span<kAaguidLength>(it->second.GetBytestring())); it = response_map.find(CBOR(2)); if (it != response_map.end()) { diff --git a/chromium/device/fido/fake_fido_discovery_unittest.cc b/chromium/device/fido/fake_fido_discovery_unittest.cc index d3a8c37d642..61a9bef8856 100644 --- a/chromium/device/fido/fake_fido_discovery_unittest.cc +++ b/chromium/device/fido/fake_fido_discovery_unittest.cc @@ -118,9 +118,6 @@ TEST_F(FakeFidoDiscoveryTest, AddDevice) { auto device0 = std::make_unique<MockFidoDevice>(); EXPECT_CALL(*device0, GetId()).WillOnce(::testing::Return("device0")); - device0->ExpectCtap2CommandAndRespondWith( - CtapRequestCommand::kAuthenticatorGetInfo, - test_data::kTestAuthenticatorGetInfoResponse); base::RunLoop device0_done; EXPECT_CALL(observer, DeviceAdded(&discovery, ::testing::_)) .WillOnce(testing::InvokeWithoutArgs( @@ -135,9 +132,6 @@ TEST_F(FakeFidoDiscoveryTest, AddDevice) { auto device1 = std::make_unique<MockFidoDevice>(); EXPECT_CALL(*device1, GetId()).WillOnce(::testing::Return("device1")); - device1->ExpectCtap2CommandAndRespondWith( - CtapRequestCommand::kAuthenticatorGetInfo, - test_data::kTestAuthenticatorGetInfoResponse); base::RunLoop device1_done; EXPECT_CALL(observer, DeviceAdded(&discovery, ::testing::_)) .WillOnce(testing::InvokeWithoutArgs( diff --git a/chromium/device/fido/fido_authenticator.h b/chromium/device/fido/fido_authenticator.h index 258c9bf6a33..b2a254d23a5 100644 --- a/chromium/device/fido/fido_authenticator.h +++ b/chromium/device/fido/fido_authenticator.h @@ -10,9 +10,11 @@ #include "base/callback_forward.h" #include "base/component_export.h" #include "base/macros.h" +#include "base/memory/weak_ptr.h" #include "base/optional.h" #include "device/fido/authenticator_get_assertion_response.h" #include "device/fido/authenticator_make_credential_response.h" +#include "device/fido/fido_transport_protocol.h" namespace device { @@ -35,6 +37,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoAuthenticator { FidoAuthenticator() = default; virtual ~FidoAuthenticator() = default; + // Sends GetInfo request to connected authenticator. Once response to GetInfo + // call is received, |callback| is invoked. Below MakeCredential() and + // GetAssertion() must only called after |callback| is invoked. + virtual void InitializeAuthenticator(base::OnceClosure callback) = 0; virtual void MakeCredential( CtapMakeCredentialRequest request, MakeCredentialCallback callback) = 0; @@ -43,6 +49,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoAuthenticator { virtual void Cancel() = 0; virtual std::string GetId() const = 0; virtual const AuthenticatorSupportedOptions& Options() const = 0; + virtual FidoTransportProtocol AuthenticatorTransport() const = 0; + virtual base::WeakPtr<FidoAuthenticator> GetWeakPtr() = 0; private: DISALLOW_COPY_AND_ASSIGN(FidoAuthenticator); diff --git a/chromium/device/fido/fido_ble_connection.cc b/chromium/device/fido/fido_ble_connection.cc deleted file mode 100644 index f6eff882dfa..00000000000 --- a/chromium/device/fido/fido_ble_connection.cc +++ /dev/null @@ -1,593 +0,0 @@ -// 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 "device/fido/fido_ble_connection.h" - -#include <utility> - -#include "base/barrier_closure.h" -#include "base/bind.h" -#include "base/callback_helpers.h" -#include "base/logging.h" -#include "device/bluetooth/bluetooth_adapter_factory.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/bluetooth_uuid.h" -#include "device/fido/fido_ble_uuids.h" - -namespace device { - -namespace { - -constexpr const char* ToString(BluetoothDevice::ConnectErrorCode error_code) { - switch (error_code) { - case BluetoothDevice::ERROR_AUTH_CANCELED: - return "ERROR_AUTH_CANCELED"; - case BluetoothDevice::ERROR_AUTH_FAILED: - return "ERROR_AUTH_FAILED"; - case BluetoothDevice::ERROR_AUTH_REJECTED: - return "ERROR_AUTH_REJECTED"; - case BluetoothDevice::ERROR_AUTH_TIMEOUT: - return "ERROR_AUTH_TIMEOUT"; - case BluetoothDevice::ERROR_FAILED: - return "ERROR_FAILED"; - case BluetoothDevice::ERROR_INPROGRESS: - return "ERROR_INPROGRESS"; - case BluetoothDevice::ERROR_UNKNOWN: - return "ERROR_UNKNOWN"; - case BluetoothDevice::ERROR_UNSUPPORTED_DEVICE: - return "ERROR_UNSUPPORTED_DEVICE"; - default: - NOTREACHED(); - return ""; - } -} - -constexpr const char* ToString(BluetoothGattService::GattErrorCode error_code) { - switch (error_code) { - case BluetoothGattService::GATT_ERROR_UNKNOWN: - return "GATT_ERROR_UNKNOWN"; - case BluetoothGattService::GATT_ERROR_FAILED: - return "GATT_ERROR_FAILED"; - case BluetoothGattService::GATT_ERROR_IN_PROGRESS: - return "GATT_ERROR_IN_PROGRESS"; - case BluetoothGattService::GATT_ERROR_INVALID_LENGTH: - return "GATT_ERROR_INVALID_LENGTH"; - case BluetoothGattService::GATT_ERROR_NOT_PERMITTED: - return "GATT_ERROR_NOT_PERMITTED"; - case BluetoothGattService::GATT_ERROR_NOT_AUTHORIZED: - return "GATT_ERROR_NOT_AUTHORIZED"; - case BluetoothGattService::GATT_ERROR_NOT_PAIRED: - return "GATT_ERROR_NOT_PAIRED"; - case BluetoothGattService::GATT_ERROR_NOT_SUPPORTED: - return "GATT_ERROR_NOT_SUPPORTED"; - default: - NOTREACHED(); - return ""; - } -} - -} // namespace - -FidoBleConnection::FidoBleConnection(std::string device_address) - : address_(std::move(device_address)), weak_factory_(this) {} - -FidoBleConnection::FidoBleConnection( - std::string device_address, - ConnectionStatusCallback connection_status_callback, - ReadCallback read_callback) - : address_(std::move(device_address)), - connection_status_callback_(std::move(connection_status_callback)), - read_callback_(std::move(read_callback)), - weak_factory_(this) { - DCHECK(!address_.empty()); -} - -FidoBleConnection::~FidoBleConnection() { - if (adapter_) - adapter_->RemoveObserver(this); -} - -const BluetoothDevice* FidoBleConnection::GetBleDevice() const { - return adapter_ ? adapter_->GetDevice(address()) : nullptr; -} - -void FidoBleConnection::Connect() { - BluetoothAdapterFactory::GetAdapter( - base::Bind(&FidoBleConnection::OnGetAdapter, weak_factory_.GetWeakPtr())); -} - -void FidoBleConnection::ReadControlPointLength( - ControlPointLengthCallback callback) { - const BluetoothRemoteGattService* u2f_service = GetFidoService(); - if (!u2f_service) { - std::move(callback).Run(base::nullopt); - return; - } - - BluetoothRemoteGattCharacteristic* control_point_length = - u2f_service->GetCharacteristic(*control_point_length_id_); - if (!control_point_length) { - DLOG(ERROR) << "No Control Point Length characteristic present."; - std::move(callback).Run(base::nullopt); - return; - } - - auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback)); - control_point_length->ReadRemoteCharacteristic( - base::Bind(OnReadControlPointLength, copyable_callback), - base::Bind(OnReadControlPointLengthError, copyable_callback)); -} - -void FidoBleConnection::ReadServiceRevisions( - ServiceRevisionsCallback callback) { - const BluetoothRemoteGattService* u2f_service = GetFidoService(); - if (!u2f_service) { - std::move(callback).Run({}); - return; - } - - DCHECK(service_revision_id_ || service_revision_bitfield_id_); - BluetoothRemoteGattCharacteristic* service_revision = - service_revision_id_ - ? u2f_service->GetCharacteristic(*service_revision_id_) - : nullptr; - - BluetoothRemoteGattCharacteristic* service_revision_bitfield = - service_revision_bitfield_id_ - ? u2f_service->GetCharacteristic(*service_revision_bitfield_id_) - : nullptr; - - if (!service_revision && !service_revision_bitfield) { - DLOG(ERROR) << "Service Revision Characteristics do not exist."; - std::move(callback).Run({}); - return; - } - - // Start from a clean state. - service_revisions_.clear(); - - // In order to obtain the full set of supported service revisions it is - // possible that both the |service_revision_| and |service_revision_bitfield_| - // characteristics must be read. Potentially we need to take the union of - // the individually supported service revisions, hence the indirection to - // ReturnServiceRevisions() is introduced. - base::Closure copyable_callback = base::AdaptCallbackForRepeating( - base::BindOnce(&FidoBleConnection::ReturnServiceRevisions, - weak_factory_.GetWeakPtr(), std::move(callback))); - - // If the Service Revision Bitfield characteristic is not present, only - // attempt to read the Service Revision characteristic. - if (!service_revision_bitfield) { - service_revision->ReadRemoteCharacteristic( - base::Bind(&FidoBleConnection::OnReadServiceRevision, - weak_factory_.GetWeakPtr(), copyable_callback), - base::Bind(&FidoBleConnection::OnReadServiceRevisionError, - weak_factory_.GetWeakPtr(), copyable_callback)); - return; - } - - // If the Service Revision characteristic is not present, only - // attempt to read the Service Revision Bitfield characteristic. - if (!service_revision) { - service_revision_bitfield->ReadRemoteCharacteristic( - base::Bind(&FidoBleConnection::OnReadServiceRevisionBitfield, - weak_factory_.GetWeakPtr(), copyable_callback), - base::Bind(&FidoBleConnection::OnReadServiceRevisionBitfieldError, - weak_factory_.GetWeakPtr(), copyable_callback)); - return; - } - - // This is the case where both characteristics are present. These reads can - // happen in parallel, but both must finish before a result can be returned. - // Hence a BarrierClosure is introduced invoking ReturnServiceRevisions() once - // both characteristic reads are done. - base::RepeatingClosure barrier_closure = - base::BarrierClosure(2, copyable_callback); - - service_revision->ReadRemoteCharacteristic( - base::Bind(&FidoBleConnection::OnReadServiceRevision, - weak_factory_.GetWeakPtr(), barrier_closure), - base::Bind(&FidoBleConnection::OnReadServiceRevisionError, - weak_factory_.GetWeakPtr(), barrier_closure)); - - service_revision_bitfield->ReadRemoteCharacteristic( - base::Bind(&FidoBleConnection::OnReadServiceRevisionBitfield, - weak_factory_.GetWeakPtr(), barrier_closure), - base::Bind(&FidoBleConnection::OnReadServiceRevisionBitfieldError, - weak_factory_.GetWeakPtr(), barrier_closure)); -} - -void FidoBleConnection::WriteControlPoint(const std::vector<uint8_t>& data, - WriteCallback callback) { - const BluetoothRemoteGattService* u2f_service = GetFidoService(); - if (!u2f_service) { - std::move(callback).Run(false); - return; - } - - BluetoothRemoteGattCharacteristic* control_point = - u2f_service->GetCharacteristic(*control_point_id_); - if (!control_point) { - DLOG(ERROR) << "Control Point characteristic not present."; - std::move(callback).Run(false); - return; - } - - // Attempt a write without response for performance reasons. Fall back to a - // confirmed write in case of failure, e.g. when the characteristic does not - // provide the required property. - if (control_point->WriteWithoutResponse(data)) { - DVLOG(2) << "Write without response succeeded."; - std::move(callback).Run(true); - } else { - auto copyable_callback = - base::AdaptCallbackForRepeating(std::move(callback)); - control_point->WriteRemoteCharacteristic( - data, base::Bind(OnWrite, copyable_callback), - base::Bind(OnWriteError, copyable_callback)); - } -} - -void FidoBleConnection::WriteServiceRevision(ServiceRevision service_revision, - WriteCallback callback) { - const BluetoothRemoteGattService* u2f_service = GetFidoService(); - if (!u2f_service) { - std::move(callback).Run(false); - return; - } - - BluetoothRemoteGattCharacteristic* service_revision_bitfield = - u2f_service->GetCharacteristic(*service_revision_bitfield_id_); - if (!service_revision_bitfield) { - DLOG(ERROR) << "Service Revision Bitfield characteristic not present."; - std::move(callback).Run(false); - return; - } - - std::vector<uint8_t> payload; - switch (service_revision) { - case ServiceRevision::VERSION_1_1: - payload.push_back(0x80); - break; - case ServiceRevision::VERSION_1_2: - payload.push_back(0x40); - break; - default: - DLOG(ERROR) - << "Write Service Revision Failed: Unsupported Service Revision."; - std::move(callback).Run(false); - return; - } - - auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback)); - service_revision_bitfield->WriteRemoteCharacteristic( - payload, base::Bind(OnWrite, copyable_callback), - base::Bind(OnWriteError, copyable_callback)); -} - -void FidoBleConnection::OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter) { - if (!adapter) { - DLOG(ERROR) << "Failed to get Adapter."; - OnConnectionError(); - return; - } - - DVLOG(2) << "Got Adapter: " << adapter->GetAddress(); - adapter_ = std::move(adapter); - adapter_->AddObserver(this); - CreateGattConnection(); -} - -void FidoBleConnection::CreateGattConnection() { - BluetoothDevice* device = adapter_->GetDevice(address_); - if (!device) { - DLOG(ERROR) << "Failed to get Device."; - OnConnectionError(); - return; - } - - device->CreateGattConnection( - base::Bind(&FidoBleConnection::OnCreateGattConnection, - weak_factory_.GetWeakPtr()), - base::Bind(&FidoBleConnection::OnCreateGattConnectionError, - weak_factory_.GetWeakPtr())); -} - -void FidoBleConnection::OnCreateGattConnection( - std::unique_ptr<BluetoothGattConnection> connection) { - connection_ = std::move(connection); - - BluetoothDevice* device = adapter_->GetDevice(address_); - if (!device) { - DLOG(ERROR) << "Failed to get Device."; - OnConnectionError(); - return; - } - - if (device->IsGattServicesDiscoveryComplete()) - ConnectToU2fService(); -} - -void FidoBleConnection::OnCreateGattConnectionError( - BluetoothDevice::ConnectErrorCode error_code) { - DLOG(ERROR) << "CreateGattConnection() failed: " << ToString(error_code); - OnConnectionError(); -} - -void FidoBleConnection::ConnectToU2fService() { - BluetoothDevice* device = adapter_->GetDevice(address_); - if (!device) { - DLOG(ERROR) << "Failed to get Device."; - OnConnectionError(); - return; - } - - DCHECK(device->IsGattServicesDiscoveryComplete()); - const std::vector<BluetoothRemoteGattService*> services = - device->GetGattServices(); - auto found = - std::find_if(services.begin(), services.end(), [](const auto* service) { - return service->GetUUID().canonical_value() == kFidoServiceUUID; - }); - - if (found == services.end()) { - DLOG(ERROR) << "Failed to get U2F Service."; - OnConnectionError(); - return; - } - - const BluetoothRemoteGattService* u2f_service = *found; - u2f_service_id_ = u2f_service->GetIdentifier(); - DVLOG(2) << "Got U2F Service: " << *u2f_service_id_; - - for (const auto* characteristic : u2f_service->GetCharacteristics()) { - // NOTE: Since GetUUID() returns a temporary |uuid| can't be a reference, - // even though canonical_value() returns a const reference. - const std::string uuid = characteristic->GetUUID().canonical_value(); - if (uuid == kFidoControlPointLengthUUID) { - control_point_length_id_ = characteristic->GetIdentifier(); - DVLOG(2) << "Got U2F Control Point Length: " << *control_point_length_id_; - } else if (uuid == kFidoControlPointUUID) { - control_point_id_ = characteristic->GetIdentifier(); - DVLOG(2) << "Got U2F Control Point: " << *control_point_id_; - } else if (uuid == kFidoStatusUUID) { - status_id_ = characteristic->GetIdentifier(); - DVLOG(2) << "Got U2F Status: " << *status_id_; - } else if (uuid == kFidoServiceRevisionUUID) { - service_revision_id_ = characteristic->GetIdentifier(); - DVLOG(2) << "Got U2F Service Revision: " << *service_revision_id_; - } else if (uuid == kFidoServiceRevisionBitfieldUUID) { - service_revision_bitfield_id_ = characteristic->GetIdentifier(); - DVLOG(2) << "Got U2F Service Revision Bitfield: " - << *service_revision_bitfield_id_; - } - } - - if (!control_point_length_id_ || !control_point_id_ || !status_id_ || - (!service_revision_id_ && !service_revision_bitfield_id_)) { - DLOG(ERROR) << "U2F characteristics missing."; - OnConnectionError(); - return; - } - - u2f_service->GetCharacteristic(*status_id_) - ->StartNotifySession( - base::Bind(&FidoBleConnection::OnStartNotifySession, - weak_factory_.GetWeakPtr()), - base::Bind(&FidoBleConnection::OnStartNotifySessionError, - weak_factory_.GetWeakPtr())); -} - -void FidoBleConnection::OnStartNotifySession( - std::unique_ptr<BluetoothGattNotifySession> notify_session) { - notify_session_ = std::move(notify_session); - DVLOG(2) << "Created notification session. Connection established."; - connection_status_callback_.Run(true); -} - -void FidoBleConnection::OnStartNotifySessionError( - BluetoothGattService::GattErrorCode error_code) { - DLOG(ERROR) << "StartNotifySession() failed: " << ToString(error_code); - OnConnectionError(); -} - -void FidoBleConnection::OnConnectionError() { - connection_status_callback_.Run(false); - - connection_.reset(); - notify_session_.reset(); - - u2f_service_id_.reset(); - control_point_length_id_.reset(); - control_point_id_.reset(); - status_id_.reset(); - service_revision_id_.reset(); - service_revision_bitfield_id_.reset(); -} - -const BluetoothRemoteGattService* FidoBleConnection::GetFidoService() const { - if (!adapter_) { - DLOG(ERROR) << "No adapter present."; - return nullptr; - } - - const BluetoothDevice* device = adapter_->GetDevice(address_); - if (!device) { - DLOG(ERROR) << "No device present."; - return nullptr; - } - - if (!u2f_service_id_) { - DLOG(ERROR) << "Unknown U2F service id."; - return nullptr; - } - - const BluetoothRemoteGattService* u2f_service = - device->GetGattService(*u2f_service_id_); - if (!u2f_service) { - DLOG(ERROR) << "No U2F service present."; - return nullptr; - } - - return u2f_service; -} - -void FidoBleConnection::DeviceAdded(BluetoothAdapter* adapter, - BluetoothDevice* device) { - if (adapter != adapter_ || device->GetAddress() != address_) - return; - CreateGattConnection(); -} - -void FidoBleConnection::DeviceAddressChanged(BluetoothAdapter* adapter, - BluetoothDevice* device, - const std::string& old_address) { - if (adapter != adapter_ || old_address != address_) - return; - address_ = device->GetAddress(); -} - -void FidoBleConnection::DeviceChanged(BluetoothAdapter* adapter, - BluetoothDevice* device) { - if (adapter != adapter_ || device->GetAddress() != address_) - return; - if (connection_ && !device->IsGattConnected()) { - DLOG(ERROR) << "GATT Disconnected: " << device->GetAddress(); - OnConnectionError(); - } -} - -void FidoBleConnection::GattCharacteristicValueChanged( - BluetoothAdapter* adapter, - BluetoothRemoteGattCharacteristic* characteristic, - const std::vector<uint8_t>& value) { - if (characteristic->GetIdentifier() != status_id_) - return; - DVLOG(2) << "Status characteristic value changed."; - read_callback_.Run(value); -} - -void FidoBleConnection::GattServicesDiscovered(BluetoothAdapter* adapter, - BluetoothDevice* device) { - if (adapter != adapter_ || device->GetAddress() != address_) - return; - ConnectToU2fService(); -} - -// static -void FidoBleConnection::OnReadControlPointLength( - ControlPointLengthCallback callback, - const std::vector<uint8_t>& value) { - if (value.size() != 2) { - DLOG(ERROR) << "Wrong Control Point Length: " << value.size() << " bytes"; - std::move(callback).Run(base::nullopt); - return; - } - - uint16_t length = (value[0] << 8) | value[1]; - DVLOG(2) << "Control Point Length: " << length; - std::move(callback).Run(length); -} - -// static -void FidoBleConnection::OnReadControlPointLengthError( - ControlPointLengthCallback callback, - BluetoothGattService::GattErrorCode error_code) { - DLOG(ERROR) << "Error reading Control Point Length: " << ToString(error_code); - std::move(callback).Run(base::nullopt); -} - -void FidoBleConnection::OnReadServiceRevision( - base::OnceClosure callback, - const std::vector<uint8_t>& value) { - std::string service_revision(value.begin(), value.end()); - DVLOG(2) << "Service Revision: " << service_revision; - - if (service_revision == "1.0") { - service_revisions_.insert(ServiceRevision::VERSION_1_0); - } else if (service_revision == "1.1") { - service_revisions_.insert(ServiceRevision::VERSION_1_1); - } else if (service_revision == "1.2") { - service_revisions_.insert(ServiceRevision::VERSION_1_2); - } else { - DLOG(ERROR) << "Unknown Service Revision: " << service_revision; - std::move(callback).Run(); - return; - } - - std::move(callback).Run(); -} - -void FidoBleConnection::OnReadServiceRevisionError( - base::OnceClosure callback, - BluetoothGattService::GattErrorCode error_code) { - DLOG(ERROR) << "Error reading Service Revision: " << ToString(error_code); - std::move(callback).Run(); -} - -void FidoBleConnection::OnReadServiceRevisionBitfield( - base::OnceClosure callback, - const std::vector<uint8_t>& value) { - if (value.empty()) { - DLOG(ERROR) << "Service Revision Bitfield is empty."; - std::move(callback).Run(); - return; - } - - if (value.size() != 1u) { - DLOG(ERROR) << "Service Revision Bitfield has unexpected size: " - << value.size() << ". Ignoring all but the first byte."; - } - - const uint8_t bitset = value[0]; - if (bitset & 0x3F) { - DLOG(ERROR) << "Service Revision Bitfield has unexpected bits set: 0x" - << std::hex << (bitset & 0x3F) - << ". Ignoring all but the first two bits."; - } - - if (bitset & 0x80) { - service_revisions_.insert(ServiceRevision::VERSION_1_1); - DVLOG(2) << "Detected Support for Service Revision 1.1"; - } - - if (bitset & 0x40) { - service_revisions_.insert(ServiceRevision::VERSION_1_2); - DVLOG(2) << "Detected Support for Service Revision 1.2"; - } - - std::move(callback).Run(); -} - -void FidoBleConnection::OnReadServiceRevisionBitfieldError( - base::OnceClosure callback, - BluetoothGattService::GattErrorCode error_code) { - DLOG(ERROR) << "Error reading Service Revision Bitfield: " - << ToString(error_code); - std::move(callback).Run(); -} - -void FidoBleConnection::ReturnServiceRevisions( - ServiceRevisionsCallback callback) { - std::move(callback).Run(std::move(service_revisions_)); -} - -// static -void FidoBleConnection::OnWrite(WriteCallback callback) { - DVLOG(2) << "Write succeeded."; - std::move(callback).Run(true); -} - -// static -void FidoBleConnection::OnWriteError( - WriteCallback callback, - BluetoothGattService::GattErrorCode error_code) { - DLOG(ERROR) << "Write Failed: " << ToString(error_code); - std::move(callback).Run(false); -} - -} // namespace device diff --git a/chromium/device/fido/fido_ble_connection_unittest.cc b/chromium/device/fido/fido_ble_connection_unittest.cc deleted file mode 100644 index 4e767f5e977..00000000000 --- a/chromium/device/fido/fido_ble_connection_unittest.cc +++ /dev/null @@ -1,708 +0,0 @@ -// 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 "device/fido/fido_ble_connection.h" - -#include <utility> - -#include "base/bind.h" -#include "base/run_loop.h" -#include "base/strings/string_piece.h" -#include "base/test/scoped_task_environment.h" -#include "build/build_config.h" -#include "device/bluetooth/bluetooth_adapter_factory.h" -#include "device/bluetooth/test/bluetooth_test.h" -#include "device/bluetooth/test/mock_bluetooth_adapter.h" -#include "device/bluetooth/test/mock_bluetooth_device.h" -#include "device/bluetooth/test/mock_bluetooth_gatt_characteristic.h" -#include "device/bluetooth/test/mock_bluetooth_gatt_connection.h" -#include "device/bluetooth/test/mock_bluetooth_gatt_notify_session.h" -#include "device/bluetooth/test/mock_bluetooth_gatt_service.h" -#include "device/fido/fido_ble_uuids.h" -#include "device/fido/test_callback_receiver.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" - -#if defined(OS_ANDROID) -#include "device/bluetooth/test/bluetooth_test_android.h" -#elif defined(OS_MACOSX) -#include "device/bluetooth/test/bluetooth_test_mac.h" -#elif defined(OS_WIN) -#include "device/bluetooth/test/bluetooth_test_win.h" -#elif defined(OS_CHROMEOS) || defined(OS_LINUX) -#include "device/bluetooth/test/bluetooth_test_bluez.h" -#endif - -namespace device { - -using ::testing::_; -using ::testing::ElementsAre; -using ::testing::Invoke; -using ::testing::IsEmpty; -using ::testing::Return; - -using NiceMockBluetoothAdapter = ::testing::NiceMock<MockBluetoothAdapter>; -using NiceMockBluetoothDevice = ::testing::NiceMock<MockBluetoothDevice>; -using NiceMockBluetoothGattService = - ::testing::NiceMock<MockBluetoothGattService>; -using NiceMockBluetoothGattCharacteristic = - ::testing::NiceMock<MockBluetoothGattCharacteristic>; -using NiceMockBluetoothGattConnection = - ::testing::NiceMock<MockBluetoothGattConnection>; -using NiceMockBluetoothGattNotifySession = - ::testing::NiceMock<MockBluetoothGattNotifySession>; - -namespace { - -std::vector<uint8_t> ToByteVector(base::StringPiece str) { - return std::vector<uint8_t>(str.begin(), str.end()); -} - -BluetoothDevice* GetMockDevice(MockBluetoothAdapter* adapter, - const std::string& address) { - const std::vector<BluetoothDevice*> devices = adapter->GetMockDevices(); - auto found = std::find_if(devices.begin(), devices.end(), - [&address](const auto* device) { - return device->GetAddress() == address; - }); - return found != devices.end() ? *found : nullptr; -} - -class TestConnectionStatusCallback { - public: - void OnStatus(bool status) { - status_ = status; - run_loop_->Quit(); - } - - bool WaitForResult() { - run_loop_->Run(); - run_loop_.emplace(); - return status_; - } - - FidoBleConnection::ConnectionStatusCallback GetCallback() { - return base::BindRepeating(&TestConnectionStatusCallback::OnStatus, - base::Unretained(this)); - } - - private: - bool status_ = false; - base::Optional<base::RunLoop> run_loop_{base::in_place}; -}; - -class TestReadCallback { - public: - void OnRead(std::vector<uint8_t> value) { - value_ = std::move(value); - run_loop_->Quit(); - } - - const std::vector<uint8_t> WaitForResult() { - run_loop_->Run(); - run_loop_.emplace(); - return value_; - } - - FidoBleConnection::ReadCallback GetCallback() { - return base::BindRepeating(&TestReadCallback::OnRead, - base::Unretained(this)); - } - - private: - std::vector<uint8_t> value_; - base::Optional<base::RunLoop> run_loop_{base::in_place}; -}; - -using TestReadControlPointLengthCallback = - test::ValueCallbackReceiver<base::Optional<uint16_t>>; - -using TestReadServiceRevisionsCallback = - test::ValueCallbackReceiver<std::set<FidoBleConnection::ServiceRevision>>; - -using TestWriteCallback = test::ValueCallbackReceiver<bool>; -} // namespace - -class FidoBleConnectionTest : public ::testing::Test { - public: - FidoBleConnectionTest() { - ON_CALL(*adapter_, GetDevice(_)) - .WillByDefault(Invoke([this](const std::string& address) { - return GetMockDevice(adapter_.get(), address); - })); - - BluetoothAdapterFactory::SetAdapterForTesting(adapter_); - } - - void AddU2Device(const std::string& device_address) { - auto u2f_device = std::make_unique<NiceMockBluetoothDevice>( - adapter_.get(), /* bluetooth_class */ 0u, - BluetoothTest::kTestDeviceNameU2f, device_address, /* paired */ true, - /* connected */ false); - u2f_device_ = u2f_device.get(); - adapter_->AddMockDevice(std::move(u2f_device)); - - ON_CALL(*u2f_device_, GetGattServices()) - .WillByDefault( - Invoke(u2f_device_, &MockBluetoothDevice::GetMockServices)); - - ON_CALL(*u2f_device_, GetGattService(_)) - .WillByDefault( - Invoke(u2f_device_, &MockBluetoothDevice::GetMockService)); - AddU2fService(); - } - - void SetupConnectingU2fDevice(const std::string& device_address) { - auto run_cb_with_connection = [this, &device_address]( - const auto& callback, - const auto& error_callback) { - auto connection = std::make_unique<NiceMockBluetoothGattConnection>( - adapter_, device_address); - connection_ = connection.get(); - callback.Run(std::move(connection)); - }; - - auto run_cb_with_notify_session = [this](const auto& callback, - const auto& error_callback) { - auto notify_session = - std::make_unique<NiceMockBluetoothGattNotifySession>( - u2f_status_->GetWeakPtr()); - notify_session_ = notify_session.get(); - callback.Run(std::move(notify_session)); - }; - - ON_CALL(*u2f_device_, CreateGattConnection(_, _)) - .WillByDefault(Invoke(run_cb_with_connection)); - - ON_CALL(*u2f_device_, IsGattServicesDiscoveryComplete()) - .WillByDefault(Return(true)); - - ON_CALL(*u2f_status_, StartNotifySession(_, _)) - .WillByDefault(Invoke(run_cb_with_notify_session)); - } - - void SimulateDisconnect(const std::string& device_address) { - if (u2f_device_->GetAddress() != device_address) - return; - - u2f_device_->SetConnected(false); - adapter_->NotifyDeviceChanged(u2f_device_); - } - - void SimulateDeviceAddressChange(const std::string& old_address, - const std::string& new_address) { - if (!u2f_device_ || u2f_device_->GetAddress() != old_address) - return; - - ON_CALL(*u2f_device_, GetAddress()).WillByDefault(Return(new_address)); - - adapter_->NotifyDeviceChanged(u2f_device_); - for (auto& observer : adapter_->GetObservers()) - observer.DeviceAddressChanged(adapter_.get(), u2f_device_, old_address); - } - - void NotifyDeviceAdded(const std::string& device_address) { - auto* device = adapter_->GetDevice(device_address); - if (!device) - return; - - for (auto& observer : adapter_->GetObservers()) - observer.DeviceAdded(adapter_.get(), device); - } - - void NotifyStatusChanged(const std::vector<uint8_t>& value) { - for (auto& observer : adapter_->GetObservers()) - observer.GattCharacteristicValueChanged(adapter_.get(), u2f_status_, - value); - } - - void SetNextReadControlPointLengthReponse(bool success, - const std::vector<uint8_t>& value) { - EXPECT_CALL(*u2f_control_point_length_, ReadRemoteCharacteristic(_, _)) - .WillOnce(Invoke([success, value](const auto& callback, - const auto& error_callback) { - success ? callback.Run(value) - : error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED); - })); - } - - void SetNextReadServiceRevisionResponse(bool success, - const std::vector<uint8_t>& value) { - EXPECT_CALL(*u2f_service_revision_, ReadRemoteCharacteristic(_, _)) - .WillOnce(Invoke([success, value](const auto& callback, - const auto& error_callback) { - success ? callback.Run(value) - : error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED); - })); - } - - void SetNextReadServiceRevisionBitfieldResponse( - bool success, - const std::vector<uint8_t>& value) { - EXPECT_CALL(*u2f_service_revision_bitfield_, ReadRemoteCharacteristic(_, _)) - .WillOnce(Invoke([success, value](const auto& callback, - const auto& error_callback) { - success ? callback.Run(value) - : error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED); - })); - } - - void SetNextWriteControlPointResponse(bool success) { - EXPECT_CALL(*u2f_control_point_, WriteRemoteCharacteristic(_, _, _)) - .WillOnce(Invoke([success](const auto& data, const auto& callback, - const auto& error_callback) { - success ? callback.Run() - : error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED); - })); - } - - void SetNextWriteServiceRevisionResponse(bool success) { - EXPECT_CALL(*u2f_service_revision_bitfield_, - WriteRemoteCharacteristic(_, _, _)) - .WillOnce(Invoke([success](const auto& data, const auto& callback, - const auto& error_callback) { - success ? callback.Run() - : error_callback.Run(BluetoothGattService::GATT_ERROR_FAILED); - })); - } - - void AddU2fService() { - auto u2f_service = std::make_unique<NiceMockBluetoothGattService>( - u2f_device_, "u2f_service", BluetoothUUID(kFidoServiceUUID), - /* is_primary */ true, /* is_local */ false); - u2f_service_ = u2f_service.get(); - u2f_device_->AddMockService(std::move(u2f_service)); - - AddU2fCharacteristics(); - } - - void AddU2fCharacteristics() { - const bool is_local = false; - { - auto u2f_control_point = - std::make_unique<NiceMockBluetoothGattCharacteristic>( - u2f_service_, "u2f_control_point", - BluetoothUUID(kFidoControlPointUUID), is_local, - BluetoothGattCharacteristic::PROPERTY_WRITE, - BluetoothGattCharacteristic::PERMISSION_NONE); - u2f_control_point_ = u2f_control_point.get(); - u2f_service_->AddMockCharacteristic(std::move(u2f_control_point)); - } - - { - auto u2f_status = std::make_unique<NiceMockBluetoothGattCharacteristic>( - u2f_service_, "u2f_status", BluetoothUUID(kFidoStatusUUID), is_local, - BluetoothGattCharacteristic::PROPERTY_NOTIFY, - BluetoothGattCharacteristic::PERMISSION_NONE); - u2f_status_ = u2f_status.get(); - u2f_service_->AddMockCharacteristic(std::move(u2f_status)); - } - - { - auto u2f_control_point_length = - std::make_unique<NiceMockBluetoothGattCharacteristic>( - u2f_service_, "u2f_control_point_length", - BluetoothUUID(kFidoControlPointLengthUUID), is_local, - BluetoothGattCharacteristic::PROPERTY_READ, - BluetoothGattCharacteristic::PERMISSION_NONE); - u2f_control_point_length_ = u2f_control_point_length.get(); - u2f_service_->AddMockCharacteristic(std::move(u2f_control_point_length)); - } - - { - auto u2f_service_revision = - std::make_unique<NiceMockBluetoothGattCharacteristic>( - u2f_service_, "u2f_service_revision", - BluetoothUUID(kFidoServiceRevisionUUID), is_local, - BluetoothGattCharacteristic::PROPERTY_READ, - BluetoothGattCharacteristic::PERMISSION_NONE); - u2f_service_revision_ = u2f_service_revision.get(); - u2f_service_->AddMockCharacteristic(std::move(u2f_service_revision)); - } - - { - auto u2f_service_revision_bitfield = - std::make_unique<NiceMockBluetoothGattCharacteristic>( - u2f_service_, "u2f_service_revision_bitfield", - BluetoothUUID(kFidoServiceRevisionBitfieldUUID), is_local, - BluetoothGattCharacteristic::PROPERTY_READ | - BluetoothGattCharacteristic::PROPERTY_WRITE, - BluetoothGattCharacteristic::PERMISSION_NONE); - u2f_service_revision_bitfield_ = u2f_service_revision_bitfield.get(); - u2f_service_->AddMockCharacteristic( - std::move(u2f_service_revision_bitfield)); - } - } - - private: - base::test::ScopedTaskEnvironment scoped_task_environment_; - - scoped_refptr<MockBluetoothAdapter> adapter_ = - base::MakeRefCounted<NiceMockBluetoothAdapter>(); - - MockBluetoothDevice* u2f_device_; - MockBluetoothGattService* u2f_service_; - - MockBluetoothGattCharacteristic* u2f_control_point_; - MockBluetoothGattCharacteristic* u2f_status_; - MockBluetoothGattCharacteristic* u2f_control_point_length_; - MockBluetoothGattCharacteristic* u2f_service_revision_; - MockBluetoothGattCharacteristic* u2f_service_revision_bitfield_; - - MockBluetoothGattConnection* connection_; - MockBluetoothGattNotifySession* notify_session_; -}; - -TEST_F(FidoBleConnectionTest, Address) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - auto connect_do_nothing = [](bool) {}; - auto read_do_nothing = [](std::vector<uint8_t>) {}; - - FidoBleConnection connection(device_address, - base::BindRepeating(connect_do_nothing), - base::BindRepeating(read_do_nothing)); - connection.Connect(); - EXPECT_EQ(device_address, connection.address()); - AddU2Device(device_address); - - SimulateDeviceAddressChange(device_address, "new_device_address"); - EXPECT_EQ("new_device_address", connection.address()); -} - -TEST_F(FidoBleConnectionTest, DeviceNotPresent) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - TestConnectionStatusCallback connection_status_callback; - auto do_nothing = [](std::vector<uint8_t>) {}; - - FidoBleConnection connection(device_address, - connection_status_callback.GetCallback(), - base::BindRepeating(do_nothing)); - connection.Connect(); - bool result = connection_status_callback.WaitForResult(); - EXPECT_FALSE(result); -} - -TEST_F(FidoBleConnectionTest, PreConnected) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - TestConnectionStatusCallback connection_status_callback; - AddU2Device(device_address); - SetupConnectingU2fDevice(device_address); - - auto do_nothing = [](std::vector<uint8_t>) {}; - FidoBleConnection connection(device_address, - connection_status_callback.GetCallback(), - base::BindRepeating(do_nothing)); - connection.Connect(); - EXPECT_TRUE(connection_status_callback.WaitForResult()); -} - -TEST_F(FidoBleConnectionTest, PostConnected) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - TestConnectionStatusCallback connection_status_callback; - auto do_nothing = [](std::vector<uint8_t>) {}; - FidoBleConnection connection(device_address, - connection_status_callback.GetCallback(), - base::BindRepeating(do_nothing)); - connection.Connect(); - bool result = connection_status_callback.WaitForResult(); - EXPECT_FALSE(result); - - AddU2Device(device_address); - SetupConnectingU2fDevice(device_address); - NotifyDeviceAdded(device_address); - EXPECT_TRUE(connection_status_callback.WaitForResult()); -} - -TEST_F(FidoBleConnectionTest, DeviceDisconnect) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - TestConnectionStatusCallback connection_status_callback; - - AddU2Device(device_address); - SetupConnectingU2fDevice(device_address); - auto do_nothing = [](std::vector<uint8_t>) {}; - FidoBleConnection connection(device_address, - connection_status_callback.GetCallback(), - base::BindRepeating(do_nothing)); - connection.Connect(); - bool result = connection_status_callback.WaitForResult(); - EXPECT_TRUE(result); - - SimulateDisconnect(device_address); - result = connection_status_callback.WaitForResult(); - EXPECT_FALSE(result); -} - -TEST_F(FidoBleConnectionTest, ReadStatusNotifications) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - TestConnectionStatusCallback connection_status_callback; - TestReadCallback read_callback; - - AddU2Device(device_address); - SetupConnectingU2fDevice(device_address); - FidoBleConnection connection(device_address, - connection_status_callback.GetCallback(), - read_callback.GetCallback()); - connection.Connect(); - EXPECT_TRUE(connection_status_callback.WaitForResult()); - - std::vector<uint8_t> payload = ToByteVector("foo"); - NotifyStatusChanged(payload); - EXPECT_EQ(payload, read_callback.WaitForResult()); - - payload = ToByteVector("bar"); - NotifyStatusChanged(payload); - EXPECT_EQ(payload, read_callback.WaitForResult()); -} - -TEST_F(FidoBleConnectionTest, ReadControlPointLength) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - TestConnectionStatusCallback connection_status_callback; - AddU2Device(device_address); - SetupConnectingU2fDevice(device_address); - auto read_do_nothing = [](std::vector<uint8_t>) {}; - - FidoBleConnection connection(device_address, - connection_status_callback.GetCallback(), - base::BindRepeating(read_do_nothing)); - connection.Connect(); - EXPECT_TRUE(connection_status_callback.WaitForResult()); - - TestReadControlPointLengthCallback length_callback; - SetNextReadControlPointLengthReponse(false, {}); - connection.ReadControlPointLength(length_callback.callback()); - EXPECT_EQ(base::nullopt, length_callback.value()); - - // The Control Point Length should consist of exactly two bytes, hence we - // EXPECT_EQ(base::nullopt) for payloads of size 0, 1 and 3. - SetNextReadControlPointLengthReponse(true, {}); - connection.ReadControlPointLength(length_callback.callback()); - EXPECT_EQ(base::nullopt, length_callback.value()); - - SetNextReadControlPointLengthReponse(true, {0xAB}); - connection.ReadControlPointLength(length_callback.callback()); - EXPECT_EQ(base::nullopt, length_callback.value()); - - SetNextReadControlPointLengthReponse(true, {0xAB, 0xCD}); - connection.ReadControlPointLength(length_callback.callback()); - EXPECT_EQ(0xABCD, *length_callback.value()); - - SetNextReadControlPointLengthReponse(true, {0xAB, 0xCD, 0xEF}); - connection.ReadControlPointLength(length_callback.callback()); - EXPECT_EQ(base::nullopt, length_callback.value()); -} - -TEST_F(FidoBleConnectionTest, ReadServiceRevisions) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - TestConnectionStatusCallback connection_status_callback; - AddU2Device(device_address); - SetupConnectingU2fDevice(device_address); - auto read_do_nothing = [](std::vector<uint8_t>) {}; - - FidoBleConnection connection(device_address, - connection_status_callback.GetCallback(), - base::BindRepeating(read_do_nothing)); - connection.Connect(); - EXPECT_TRUE(connection_status_callback.WaitForResult()); - - TestReadServiceRevisionsCallback revisions_callback; - SetNextReadServiceRevisionResponse(false, {}); - SetNextReadServiceRevisionBitfieldResponse(false, {}); - connection.ReadServiceRevisions(revisions_callback.callback()); - EXPECT_THAT(revisions_callback.value(), IsEmpty()); - - SetNextReadServiceRevisionResponse(true, ToByteVector("bogus")); - SetNextReadServiceRevisionBitfieldResponse(false, {}); - connection.ReadServiceRevisions(revisions_callback.callback()); - EXPECT_THAT(revisions_callback.value(), IsEmpty()); - - SetNextReadServiceRevisionResponse(true, ToByteVector("1.0")); - SetNextReadServiceRevisionBitfieldResponse(false, {}); - connection.ReadServiceRevisions(revisions_callback.callback()); - EXPECT_THAT(revisions_callback.value(), - ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_0)); - - SetNextReadServiceRevisionResponse(true, ToByteVector("1.1")); - SetNextReadServiceRevisionBitfieldResponse(false, {}); - connection.ReadServiceRevisions(revisions_callback.callback()); - EXPECT_THAT(revisions_callback.value(), - ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_1)); - - SetNextReadServiceRevisionResponse(true, ToByteVector("1.2")); - SetNextReadServiceRevisionBitfieldResponse(false, {}); - connection.ReadServiceRevisions(revisions_callback.callback()); - EXPECT_THAT(revisions_callback.value(), - ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_2)); - - // Version 1.3 currently does not exist, so this should be treated as an - // error. - SetNextReadServiceRevisionResponse(true, ToByteVector("1.3")); - SetNextReadServiceRevisionBitfieldResponse(false, {}); - connection.ReadServiceRevisions(revisions_callback.callback()); - EXPECT_THAT(revisions_callback.value(), IsEmpty()); - - SetNextReadServiceRevisionResponse(false, {}); - SetNextReadServiceRevisionBitfieldResponse(true, {0x00}); - connection.ReadServiceRevisions(revisions_callback.callback()); - EXPECT_THAT(revisions_callback.value(), IsEmpty()); - - SetNextReadServiceRevisionResponse(false, {}); - SetNextReadServiceRevisionBitfieldResponse(true, {0x80}); - connection.ReadServiceRevisions(revisions_callback.callback()); - EXPECT_THAT(revisions_callback.value(), - ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_1)); - - SetNextReadServiceRevisionResponse(false, {}); - SetNextReadServiceRevisionBitfieldResponse(true, {0x40}); - connection.ReadServiceRevisions(revisions_callback.callback()); - EXPECT_THAT(revisions_callback.value(), - ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_2)); - - SetNextReadServiceRevisionResponse(false, {}); - SetNextReadServiceRevisionBitfieldResponse(true, {0xC0}); - connection.ReadServiceRevisions(revisions_callback.callback()); - EXPECT_THAT(revisions_callback.value(), - ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_1, - FidoBleConnection::ServiceRevision::VERSION_1_2)); - - // All bits except the first two should be ignored. - SetNextReadServiceRevisionResponse(false, {}); - SetNextReadServiceRevisionBitfieldResponse(true, {0xFF}); - connection.ReadServiceRevisions(revisions_callback.callback()); - EXPECT_THAT(revisions_callback.value(), - ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_1, - FidoBleConnection::ServiceRevision::VERSION_1_2)); - - // All bytes except the first one should be ignored. - SetNextReadServiceRevisionResponse(false, {}); - SetNextReadServiceRevisionBitfieldResponse(true, {0xC0, 0xFF}); - connection.ReadServiceRevisions(revisions_callback.callback()); - EXPECT_THAT(revisions_callback.value(), - ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_1, - FidoBleConnection::ServiceRevision::VERSION_1_2)); - - // The combination of a service revision string and bitfield should be - // supported as well. - SetNextReadServiceRevisionResponse(true, ToByteVector("1.0")); - SetNextReadServiceRevisionBitfieldResponse(true, {0xC0}); - connection.ReadServiceRevisions(revisions_callback.callback()); - EXPECT_THAT(revisions_callback.value(), - ElementsAre(FidoBleConnection::ServiceRevision::VERSION_1_0, - FidoBleConnection::ServiceRevision::VERSION_1_1, - FidoBleConnection::ServiceRevision::VERSION_1_2)); -} - -TEST_F(FidoBleConnectionTest, WriteControlPoint) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - TestConnectionStatusCallback connection_status_callback; - AddU2Device(device_address); - SetupConnectingU2fDevice(device_address); - auto read_do_nothing = [](std::vector<uint8_t>) {}; - - FidoBleConnection connection(device_address, - connection_status_callback.GetCallback(), - base::BindRepeating(read_do_nothing)); - connection.Connect(); - bool result = connection_status_callback.WaitForResult(); - EXPECT_TRUE(result); - - TestWriteCallback write_callback; - SetNextWriteControlPointResponse(false); - connection.WriteControlPoint({}, write_callback.callback()); - result = write_callback.value(); - EXPECT_FALSE(result); - - SetNextWriteControlPointResponse(true); - connection.WriteControlPoint({}, write_callback.callback()); - result = write_callback.value(); - EXPECT_TRUE(result); -} - -TEST_F(FidoBleConnectionTest, WriteServiceRevision) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - TestConnectionStatusCallback connection_status_callback; - AddU2Device(device_address); - SetupConnectingU2fDevice(device_address); - auto read_do_nothing = [](std::vector<uint8_t>) {}; - - FidoBleConnection connection(device_address, - connection_status_callback.GetCallback(), - base::BindRepeating(read_do_nothing)); - connection.Connect(); - bool result = connection_status_callback.WaitForResult(); - EXPECT_TRUE(result); - - // Expect that errors are properly propagated. - TestWriteCallback write_callback; - SetNextWriteServiceRevisionResponse(false); - connection.WriteServiceRevision( - FidoBleConnection::ServiceRevision::VERSION_1_1, - write_callback.callback()); - result = write_callback.value(); - EXPECT_FALSE(result); - - // Expect a successful write of version 1.1. - SetNextWriteServiceRevisionResponse(true); - connection.WriteServiceRevision( - FidoBleConnection::ServiceRevision::VERSION_1_1, - write_callback.callback()); - result = write_callback.value(); - EXPECT_TRUE(result); - - // Expect a successful write of version 1.2. - SetNextWriteServiceRevisionResponse(true); - connection.WriteServiceRevision( - FidoBleConnection::ServiceRevision::VERSION_1_2, - write_callback.callback()); - result = write_callback.value(); - EXPECT_TRUE(result); - - // Writing version 1.0 to the bitfield is not intended, so this should fail. - connection.WriteServiceRevision( - FidoBleConnection::ServiceRevision::VERSION_1_0, - write_callback.callback()); - result = write_callback.value(); - EXPECT_FALSE(result); -} - -TEST_F(FidoBleConnectionTest, ReadsAndWriteFailWhenDisconnected) { - const std::string device_address = BluetoothTest::kTestDeviceAddress1; - TestConnectionStatusCallback connection_status_callback; - - AddU2Device(device_address); - SetupConnectingU2fDevice(device_address); - auto do_nothing = [](std::vector<uint8_t>) {}; - FidoBleConnection connection(device_address, - connection_status_callback.GetCallback(), - base::BindRepeating(do_nothing)); - connection.Connect(); - bool result = connection_status_callback.WaitForResult(); - EXPECT_TRUE(result); - - SimulateDisconnect(device_address); - result = connection_status_callback.WaitForResult(); - EXPECT_FALSE(result); - - // Reads should always fail on a disconnected device. - TestReadControlPointLengthCallback length_callback; - connection.ReadControlPointLength(length_callback.callback()); - EXPECT_EQ(base::nullopt, length_callback.value()); - - TestReadServiceRevisionsCallback revisions_callback; - connection.ReadServiceRevisions(revisions_callback.callback()); - EXPECT_THAT(revisions_callback.value(), IsEmpty()); - - // Writes should always fail on a disconnected device. - TestWriteCallback write_callback; - connection.WriteServiceRevision( - FidoBleConnection::ServiceRevision::VERSION_1_1, - write_callback.callback()); - result = write_callback.value(); - EXPECT_FALSE(result); - - connection.WriteControlPoint({}, write_callback.callback()); - result = write_callback.value(); - EXPECT_FALSE(result); -} - -} // namespace device diff --git a/chromium/device/fido/fido_constants.h b/chromium/device/fido/fido_constants.h index 184ea90ae8d..2dc2a2136ac 100644 --- a/chromium/device/fido/fido_constants.h +++ b/chromium/device/fido/fido_constants.h @@ -27,6 +27,8 @@ enum class FidoReturnCode : uint8_t { // authenticator), but none of the provided credentials were recognized by // the authenticator. kUserConsentButCredentialNotRecognized, + // The user explicitly refused to provide consent. + kUserConsentDenied, }; enum class ProtocolVersion { diff --git a/chromium/device/fido/fido_device.cc b/chromium/device/fido/fido_device.cc index e3f448c052b..b9b30015b56 100644 --- a/chromium/device/fido/fido_device.cc +++ b/chromium/device/fido/fido_device.cc @@ -42,6 +42,10 @@ bool FidoDevice::SupportedProtocolIsInitialized() { void FidoDevice::OnDeviceInfoReceived( base::OnceClosure done, base::Optional<std::vector<uint8_t>> response) { + // TODO(hongjunchoi): Add tests that verify this behavior. + if (state_ == FidoDevice::State::kDeviceError) + return; + state_ = FidoDevice::State::kReady; base::Optional<AuthenticatorGetInfoResponse> get_info_response = diff --git a/chromium/device/fido/fido_device.h b/chromium/device/fido/fido_device.h index defeec56cea..ec5a77371cf 100644 --- a/chromium/device/fido/fido_device.h +++ b/chromium/device/fido/fido_device.h @@ -17,6 +17,7 @@ #include "base/optional.h" #include "device/fido/authenticator_get_info_response.h" #include "device/fido/fido_constants.h" +#include "device/fido/fido_transport_protocol.h" namespace device { @@ -32,8 +33,21 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDevice { using DeviceCallback = base::OnceCallback<void(base::Optional<std::vector<uint8_t>>)>; - // Internal state machine states. - enum class State { kInit, kConnected, kBusy, kReady, kDeviceError }; + // Internal state machine states. kMsgError represents a state where error + // has been received from the connected device because an + // unexpected/incorrectly formatted request was sent from the client. Devices + // in this state can be recovered by re-sending a well-formed command. On the + // other hand, kDeviceError represents a state where error occurred due to + // connection failure/unknown reasons and is considered an unrecoverable + // error. + enum class State { + kInit, + kConnected, + kBusy, + kReady, + kMsgError, + kDeviceError, + }; FidoDevice(); virtual ~FidoDevice(); @@ -45,6 +59,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDevice { virtual void TryWink(WinkCallback callback) = 0; virtual void Cancel() = 0; virtual std::string GetId() const = 0; + virtual FidoTransportProtocol DeviceTransport() const = 0; + virtual base::WeakPtr<FidoDevice> GetWeakPtr() = 0; // Sends a speculative AuthenticatorGetInfo request to determine whether the // device supports the CTAP2 protocol, and initializes supported_protocol_ @@ -66,8 +82,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDevice { State state() const { return state_; } protected: - virtual base::WeakPtr<FidoDevice> GetWeakPtr() = 0; - void OnDeviceInfoReceived(base::OnceClosure done, base::Optional<std::vector<uint8_t>> response); void SetDeviceInfo(AuthenticatorGetInfoResponse device_info); diff --git a/chromium/device/fido/fido_device_authenticator.cc b/chromium/device/fido/fido_device_authenticator.cc index 93071cefe4e..afb1d8aee16 100644 --- a/chromium/device/fido/fido_device_authenticator.cc +++ b/chromium/device/fido/fido_device_authenticator.cc @@ -6,7 +6,9 @@ #include <utility> +#include "base/bind.h" #include "base/logging.h" +#include "base/threading/sequenced_task_runner_handle.h" #include "device/fido/authenticator_supported_options.h" #include "device/fido/ctap_get_assertion_request.h" #include "device/fido/ctap_make_credential_request.h" @@ -17,14 +19,22 @@ namespace device { FidoDeviceAuthenticator::FidoDeviceAuthenticator(FidoDevice* device) - : device_(device) { - DCHECK(device_->SupportedProtocolIsInitialized()); -} + : device_(device), weak_factory_(this) {} FidoDeviceAuthenticator::~FidoDeviceAuthenticator() = default; +void FidoDeviceAuthenticator::InitializeAuthenticator( + base::OnceClosure callback) { + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&FidoDevice::DiscoverSupportedProtocolAndDeviceInfo, + device()->GetWeakPtr(), std::move(callback))); +} + void FidoDeviceAuthenticator::MakeCredential(CtapMakeCredentialRequest request, MakeCredentialCallback callback) { DCHECK(!task_); + DCHECK(device_->SupportedProtocolIsInitialized()) + << "InitializeAuthenticator() must be called first."; // TODO(martinkr): Change FidoTasks to take all request parameters by const // reference, so we can avoid copying these from the RequestHandler. task_ = std::make_unique<MakeCredentialTask>(device_, std::move(request), @@ -33,6 +43,8 @@ void FidoDeviceAuthenticator::MakeCredential(CtapMakeCredentialRequest request, void FidoDeviceAuthenticator::GetAssertion(CtapGetAssertionRequest request, GetAssertionCallback callback) { + DCHECK(device_->SupportedProtocolIsInitialized()) + << "InitializeAuthenticator() must be called first."; task_ = std::make_unique<GetAssertionTask>(device_, std::move(request), std::move(callback)); } @@ -63,9 +75,17 @@ const AuthenticatorSupportedOptions& FidoDeviceAuthenticator::Options() const { return default_options; } +FidoTransportProtocol FidoDeviceAuthenticator::AuthenticatorTransport() const { + return device_->DeviceTransport(); +} + void FidoDeviceAuthenticator::SetTaskForTesting( std::unique_ptr<FidoTask> task) { task_ = std::move(task); } +base::WeakPtr<FidoAuthenticator> FidoDeviceAuthenticator::GetWeakPtr() { + return weak_factory_.GetWeakPtr(); +} + } // namespace device diff --git a/chromium/device/fido/fido_device_authenticator.h b/chromium/device/fido/fido_device_authenticator.h index ec62fefdd9c..e755f1fe0c2 100644 --- a/chromium/device/fido/fido_device_authenticator.h +++ b/chromium/device/fido/fido_device_authenticator.h @@ -5,11 +5,13 @@ #ifndef DEVICE_FIDO_FIDO_DEVICE_AUTHENTICATOR_H_ #define DEVICE_FIDO_FIDO_DEVICE_AUTHENTICATOR_H_ +#include <memory> #include <string> #include <vector> #include "base/component_export.h" #include "base/macros.h" +#include "base/memory/weak_ptr.h" #include "base/optional.h" #include "device/fido/fido_authenticator.h" @@ -30,6 +32,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator ~FidoDeviceAuthenticator() override; // FidoAuthenticator: + void InitializeAuthenticator(base::OnceClosure callback) override; void MakeCredential( CtapMakeCredentialRequest request, MakeCredentialCallback callback) override; @@ -38,6 +41,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator void Cancel() override; std::string GetId() const override; const AuthenticatorSupportedOptions& Options() const override; + FidoTransportProtocol AuthenticatorTransport() const override; + base::WeakPtr<FidoAuthenticator> GetWeakPtr() override; protected: void OnCtapMakeCredentialResponseReceived( @@ -53,6 +58,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator private: FidoDevice* const device_; std::unique_ptr<FidoTask> task_; + base::WeakPtrFactory<FidoDeviceAuthenticator> weak_factory_; DISALLOW_COPY_AND_ASSIGN(FidoDeviceAuthenticator); }; diff --git a/chromium/device/fido/fido_discovery.cc b/chromium/device/fido/fido_discovery.cc index 98d4f678947..4de2ef64f05 100644 --- a/chromium/device/fido/fido_discovery.cc +++ b/chromium/device/fido/fido_discovery.cc @@ -8,12 +8,13 @@ #include "base/bind.h" #include "build/build_config.h" -#include "device/fido/fido_ble_discovery.h" +#include "device/fido/ble/fido_ble_discovery.h" +#include "device/fido/cable/fido_cable_discovery.h" #include "device/fido/fido_device.h" // HID is not supported on Android. #if !defined(OS_ANDROID) -#include "device/fido/fido_hid_discovery.h" +#include "device/fido/hid/fido_hid_discovery.h" #endif // !defined(OS_ANDROID) namespace device { @@ -34,12 +35,9 @@ std::unique_ptr<FidoDiscovery> CreateFidoDiscoveryImpl( #endif // !defined(OS_ANDROID) case FidoTransportProtocol::kBluetoothLowEnergy: return std::make_unique<FidoBleDiscovery>(); - // FidoCaBleDiscovery is not constructed using FidoDiscovery factory - // function and instead constructed in FidoGetAssertionRequestHandler as it - // requires extensions passed on from the relying party. case FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy: - NOTREACHED() - << "Cable discovery is not constructed using factory method."; + NOTREACHED() << "Cable discovery is constructed using the dedicated " + "factory method."; return nullptr; case FidoTransportProtocol::kNearFieldCommunication: // TODO(https://crbug.com/825949): Add NFC support. @@ -52,6 +50,11 @@ std::unique_ptr<FidoDiscovery> CreateFidoDiscoveryImpl( return nullptr; } +std::unique_ptr<FidoDiscovery> CreateCableDiscoveryImpl( + std::vector<CableDiscoveryData> cable_data) { + return std::make_unique<FidoCableDiscovery>(std::move(cable_data)); +} + } // namespace FidoDiscovery::Observer::~Observer() = default; @@ -61,12 +64,22 @@ FidoDiscovery::FactoryFuncPtr FidoDiscovery::g_factory_func_ = &CreateFidoDiscoveryImpl; // static +FidoDiscovery::CableFactoryFuncPtr FidoDiscovery::g_cable_factory_func_ = + &CreateCableDiscoveryImpl; + +// static std::unique_ptr<FidoDiscovery> FidoDiscovery::Create( FidoTransportProtocol transport, service_manager::Connector* connector) { return (*g_factory_func_)(transport, connector); } +// static +std::unique_ptr<FidoDiscovery> FidoDiscovery::CreateCable( + std::vector<CableDiscoveryData> cable_data) { + return (*g_cable_factory_func_)(std::move(cable_data)); +} + FidoDiscovery::FidoDiscovery(FidoTransportProtocol transport) : transport_(transport), weak_factory_(this) {} @@ -136,11 +149,8 @@ bool FidoDiscovery::AddDevice(std::unique_ptr<FidoDevice> device) { if (!result.second) { return false; // Duplicate device id. } - FidoDevice* device_ptr = result.first->second.get(); - // Determine the device protocol version before notifying observers. - device_ptr->DiscoverSupportedProtocolAndDeviceInfo( - base::BindOnce(&FidoDiscovery::NotifyDeviceAdded, - weak_factory_.GetWeakPtr(), device_ptr)); + + NotifyDeviceAdded(result.first->second.get()); return true; } @@ -165,11 +175,15 @@ ScopedFidoDiscoveryFactory::ScopedFidoDiscoveryFactory() { original_factory_func_ = std::exchange(FidoDiscovery::g_factory_func_, &ForwardCreateFidoDiscoveryToCurrentFactory); + original_cable_factory_func_ = + std::exchange(FidoDiscovery::g_cable_factory_func_, + &ForwardCreateCableDiscoveryToCurrentFactory); } ScopedFidoDiscoveryFactory::~ScopedFidoDiscoveryFactory() { g_current_factory = nullptr; FidoDiscovery::g_factory_func_ = original_factory_func_; + FidoDiscovery::g_cable_factory_func_ = original_cable_factory_func_; } // static @@ -182,6 +196,17 @@ ScopedFidoDiscoveryFactory::ForwardCreateFidoDiscoveryToCurrentFactory( } // static +std::unique_ptr<FidoDiscovery> +ScopedFidoDiscoveryFactory::ForwardCreateCableDiscoveryToCurrentFactory( + std::vector<CableDiscoveryData> cable_data) { + DCHECK(g_current_factory); + g_current_factory->set_last_cable_data(std::move(cable_data)); + return g_current_factory->CreateFidoDiscovery( + FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy, + nullptr /* connector */); +} + +// static ScopedFidoDiscoveryFactory* ScopedFidoDiscoveryFactory::g_current_factory = nullptr; diff --git a/chromium/device/fido/fido_discovery.h b/chromium/device/fido/fido_discovery.h index 68aadcc9a9f..cfc4cada6f9 100644 --- a/chromium/device/fido/fido_discovery.h +++ b/chromium/device/fido/fido_discovery.h @@ -16,6 +16,7 @@ #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "base/strings/string_piece.h" +#include "device/fido/cable/cable_discovery_data.h" #include "device/fido/fido_transport_protocol.h" namespace service_manager { @@ -43,7 +44,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDiscovery { virtual ~Observer(); // It is guaranteed that this is never invoked synchronously from Start(). - virtual void DiscoveryStarted(FidoDiscovery* discovery, bool success) = 0; + virtual void DiscoveryStarted(FidoDiscovery* discovery, bool success) {} // It is guaranteed that DeviceAdded/DeviceRemoved() will not be invoked // before the client of FidoDiscovery calls FidoDiscovery::Start(). However, @@ -59,14 +60,17 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDiscovery { FidoDevice* device) = 0; }; - // Factory function to construct an instance that discovers authenticators on - // the given |transport| protocol. + // Factory functions to construct an instance that discovers authenticators on + // the given |transport| protocol. The first variant is for everything except + // for cloud-assisted BLE which is handled by the second variant. // // FidoTransportProtocol::kUsbHumanInterfaceDevice requires specifying a valid // |connector| on Desktop, and is not valid on Android. static std::unique_ptr<FidoDiscovery> Create( FidoTransportProtocol transport, ::service_manager::Connector* connector); + static std::unique_ptr<FidoDiscovery> CreateCable( + std::vector<CableDiscoveryData> cable_data); virtual ~FidoDiscovery(); @@ -113,7 +117,9 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDiscovery { // Factory function can be overridden by tests to construct fakes. using FactoryFuncPtr = decltype(&Create); + using CableFactoryFuncPtr = decltype(&CreateCable); static FactoryFuncPtr g_factory_func_; + static CableFactoryFuncPtr g_cable_factory_func_; const FidoTransportProtocol transport_; State state_ = State::kIdle; @@ -136,7 +142,15 @@ class COMPONENT_EXPORT(DEVICE_FIDO) ScopedFidoDiscoveryFactory { ScopedFidoDiscoveryFactory(); virtual ~ScopedFidoDiscoveryFactory(); + const std::vector<CableDiscoveryData>& last_cable_data() const { + return last_cable_data_; + } + protected: + void set_last_cable_data(std::vector<CableDiscoveryData> cable_data) { + last_cable_data_ = std::move(cable_data); + } + virtual std::unique_ptr<FidoDiscovery> CreateFidoDiscovery( FidoTransportProtocol transport, ::service_manager::Connector* connector) = 0; @@ -147,8 +161,15 @@ class COMPONENT_EXPORT(DEVICE_FIDO) ScopedFidoDiscoveryFactory { FidoTransportProtocol transport, ::service_manager::Connector* connector); + static std::unique_ptr<FidoDiscovery> + ForwardCreateCableDiscoveryToCurrentFactory( + std::vector<CableDiscoveryData> cable_data); + static ScopedFidoDiscoveryFactory* g_current_factory; + FidoDiscovery::FactoryFuncPtr original_factory_func_; + FidoDiscovery::CableFactoryFuncPtr original_cable_factory_func_; + std::vector<CableDiscoveryData> last_cable_data_; DISALLOW_COPY_AND_ASSIGN(ScopedFidoDiscoveryFactory); }; diff --git a/chromium/device/fido/fido_discovery_unittest.cc b/chromium/device/fido/fido_discovery_unittest.cc index 922c559c080..46827b87acb 100644 --- a/chromium/device/fido/fido_discovery_unittest.cc +++ b/chromium/device/fido/fido_discovery_unittest.cc @@ -102,9 +102,6 @@ TEST(FidoDiscoveryTest, TestAddRemoveDevices) { // Expect successful insertion. auto device0 = std::make_unique<MockFidoDevice>(); auto* device0_raw = device0.get(); - device0->ExpectCtap2CommandAndRespondWith( - CtapRequestCommand::kAuthenticatorGetInfo, - test_data::kTestAuthenticatorGetInfoResponse); base::RunLoop device0_done; EXPECT_CALL(observer, DeviceAdded(&discovery, device0_raw)) .WillOnce(testing::InvokeWithoutArgs( @@ -113,16 +110,11 @@ TEST(FidoDiscoveryTest, TestAddRemoveDevices) { EXPECT_TRUE(discovery.AddDevice(std::move(device0))); device0_done.Run(); ::testing::Mock::VerifyAndClearExpectations(&observer); - // Device should have been initialized as a CTAP device. - EXPECT_EQ(ProtocolVersion::kCtap, device0_raw->supported_protocol()); - EXPECT_TRUE(device0_raw->device_info()); // Expect successful insertion. base::RunLoop device1_done; auto device1 = std::make_unique<MockFidoDevice>(); auto* device1_raw = device1.get(); - device1->ExpectCtap2CommandAndRespondWith( - CtapRequestCommand::kAuthenticatorGetInfo, base::nullopt); EXPECT_CALL(observer, DeviceAdded(&discovery, device1_raw)) .WillOnce(testing::InvokeWithoutArgs( [&device1_done]() { device1_done.Quit(); })); @@ -130,9 +122,6 @@ TEST(FidoDiscoveryTest, TestAddRemoveDevices) { EXPECT_TRUE(discovery.AddDevice(std::move(device1))); device1_done.Run(); ::testing::Mock::VerifyAndClearExpectations(&observer); - // Device should have been initialized as a U2F device. - EXPECT_EQ(ProtocolVersion::kU2f, device1_raw->supported_protocol()); - EXPECT_FALSE(device1_raw->device_info()); // Inserting a device with an already present id should be prevented. auto device1_dup = std::make_unique<MockFidoDevice>(); diff --git a/chromium/device/fido/fido_parsing_utils.cc b/chromium/device/fido/fido_parsing_utils.cc index a8448dce45c..661e19335a6 100644 --- a/chromium/device/fido/fido_parsing_utils.cc +++ b/chromium/device/fido/fido_parsing_utils.cc @@ -5,6 +5,8 @@ #include "device/fido/fido_parsing_utils.h" #include "base/logging.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/stringprintf.h" namespace device { namespace fido_parsing_utils { @@ -88,5 +90,27 @@ base::StringPiece ConvertToStringPiece(base::span<const uint8_t> data) { return {reinterpret_cast<const char*>(data.data()), data.size()}; } +std::string ConvertBytesToUuid(base::span<const uint8_t, 16> bytes) { + uint64_t most_significant_bytes = 0; + for (size_t i = 0; i < sizeof(uint64_t); i++) { + most_significant_bytes |= base::strict_cast<uint64_t>(bytes[i]) + << 8 * (7 - i); + } + + uint64_t least_significant_bytes = 0; + for (size_t i = 0; i < sizeof(uint64_t); i++) { + least_significant_bytes |= base::strict_cast<uint64_t>(bytes[i + 8]) + << 8 * (7 - i); + } + + return base::StringPrintf( + "%08x-%04x-%04x-%04x-%012llx", + static_cast<unsigned int>(most_significant_bytes >> 32), + static_cast<unsigned int>((most_significant_bytes >> 16) & 0x0000ffff), + static_cast<unsigned int>(most_significant_bytes & 0x0000ffff), + static_cast<unsigned int>(least_significant_bytes >> 48), + least_significant_bytes & 0x0000ffff'ffffffffULL); +} + } // namespace fido_parsing_utils } // namespace device diff --git a/chromium/device/fido/fido_parsing_utils.h b/chromium/device/fido/fido_parsing_utils.h index 278207baa44..6bf982dad91 100644 --- a/chromium/device/fido/fido_parsing_utils.h +++ b/chromium/device/fido/fido_parsing_utils.h @@ -114,6 +114,12 @@ std::array<uint8_t, crypto::kSHA256Length> CreateSHA256Hash( COMPONENT_EXPORT(DEVICE_FIDO) base::StringPiece ConvertToStringPiece(base::span<const uint8_t> data); +// Convert byte array into GUID formatted string as defined by RFC 4122. +// As we are converting 128 bit UUID, |bytes| must be have length of 16. +// https://tools.ietf.org/html/rfc4122 +COMPONENT_EXPORT(DEVICE_FIDO) +std::string ConvertBytesToUuid(base::span<const uint8_t, 16> bytes); + } // namespace fido_parsing_utils } // namespace device diff --git a/chromium/device/fido/fido_request_handler.h b/chromium/device/fido/fido_request_handler.h index fa2e842ca8d..00cf7540a2b 100644 --- a/chromium/device/fido/fido_request_handler.h +++ b/chromium/device/fido/fido_request_handler.h @@ -27,24 +27,18 @@ class FidoRequestHandler : public FidoRequestHandlerBase { public: using CompletionCallback = base::OnceCallback<void(FidoReturnCode status_code, - base::Optional<Response> response_data)>; - - FidoRequestHandler(service_manager::Connector* connector, - const base::flat_set<FidoTransportProtocol>& transports, - CompletionCallback completion_callback) - : FidoRequestHandler(connector, - transports, - std::move(completion_callback), - AddPlatformAuthenticatorCallback()) {} + base::Optional<Response> response_data, + FidoTransportProtocol transport_used)>; + + // The |available_transports| should be the intersection of transports + // supported by the client and allowed by the relying party. FidoRequestHandler( service_manager::Connector* connector, - const base::flat_set<FidoTransportProtocol>& transports, - CompletionCallback completion_callback, - AddPlatformAuthenticatorCallback add_platform_authenticator) - : FidoRequestHandlerBase(connector, - transports, - std::move(add_platform_authenticator)), + const base::flat_set<FidoTransportProtocol>& available_transports, + CompletionCallback completion_callback) + : FidoRequestHandlerBase(connector, available_transports), completion_callback_(std::move(completion_callback)) {} + ~FidoRequestHandler() override { if (!is_complete()) CancelOngoingTasks(); @@ -64,8 +58,9 @@ class FidoRequestHandler : public FidoRequestHandlerBase { return; } - const auto return_code = ConvertDeviceResponseCodeToFidoReturnCode( - device_response_code, response_data.has_value()); + base::Optional<FidoReturnCode> return_code = + ConvertDeviceResponseCodeToFidoReturnCode(device_response_code, + response_data.has_value()); // Any authenticator response codes that do not result from user consent // imply that the authenticator should be dropped and that other on-going @@ -78,7 +73,9 @@ class FidoRequestHandler : public FidoRequestHandlerBase { // Once response has been passed to the relying party, cancel all other on // going requests. CancelOngoingTasks(authenticator->GetId()); - std::move(completion_callback_).Run(*return_code, std::move(response_data)); + std::move(completion_callback_) + .Run(*return_code, std::move(response_data), + authenticator->AuthenticatorTransport()); } private: @@ -99,6 +96,12 @@ class FidoRequestHandler : public FidoRequestHandlerBase { case CtapDeviceResponseCode::kCtap2ErrNoCredentials: return FidoReturnCode::kUserConsentButCredentialNotRecognized; + // The user explicitly denied the operation. Touch ID returns this error + // when the user cancels the macOS prompt. External authenticators may + // return it e.g. after the user fails fingerprint verification. + case CtapDeviceResponseCode::kCtap2ErrOperationDenied: + return FidoReturnCode::kUserConsentDenied; + // This error is returned by some authenticators (e.g. the "Yubico FIDO // 2" CTAP2 USB keys) during GetAssertion **before the user interacted // with the device**. The authenticator does this to avoid blinking (and @@ -110,6 +113,8 @@ class FidoRequestHandler : public FidoRequestHandlerBase { case CtapDeviceResponseCode::kCtap2ErrInvalidCredential: return base::nullopt; + // For all other errors, the authenticator will be dropped, and other + // authenticators may continue. default: return base::nullopt; } diff --git a/chromium/device/fido/fido_request_handler_base.cc b/chromium/device/fido/fido_request_handler_base.cc index 7adbfddd277..57428aaa7d1 100644 --- a/chromium/device/fido/fido_request_handler_base.cc +++ b/chromium/device/fido/fido_request_handler_base.cc @@ -6,37 +6,83 @@ #include <utility> +#include "base/barrier_closure.h" +#include "base/bind.h" +#include "base/location.h" #include "base/logging.h" #include "base/strings/string_piece.h" +#include "base/threading/sequenced_task_runner_handle.h" #include "build/build_config.h" +#include "device/fido/ble_adapter_power_manager.h" #include "device/fido/fido_device.h" #include "device/fido/fido_task.h" #include "services/service_manager/public/cpp/connector.h" namespace device { -FidoRequestHandlerBase::FidoRequestHandlerBase( - service_manager::Connector* connector, - const base::flat_set<FidoTransportProtocol>& transports) - : FidoRequestHandlerBase(connector, - transports, - AddPlatformAuthenticatorCallback()) {} +// PlatformAuthenticatorInfo -------------------------- + +PlatformAuthenticatorInfo::PlatformAuthenticatorInfo( + std::unique_ptr<FidoAuthenticator> authenticator_, + bool has_recognized_mac_touch_id_credential_) + : authenticator(std::move(authenticator_)), + has_recognized_mac_touch_id_credential( + has_recognized_mac_touch_id_credential_) {} +PlatformAuthenticatorInfo::PlatformAuthenticatorInfo( + PlatformAuthenticatorInfo&&) = default; +PlatformAuthenticatorInfo& PlatformAuthenticatorInfo::operator=( + PlatformAuthenticatorInfo&&) = default; +PlatformAuthenticatorInfo::~PlatformAuthenticatorInfo() = default; + +// FidoRequestHandlerBase::TransportAvailabilityInfo -------------------------- + +FidoRequestHandlerBase::TransportAvailabilityInfo::TransportAvailabilityInfo() = + default; + +FidoRequestHandlerBase::TransportAvailabilityInfo::TransportAvailabilityInfo( + const TransportAvailabilityInfo& data) = default; + +FidoRequestHandlerBase::TransportAvailabilityInfo& +FidoRequestHandlerBase::TransportAvailabilityInfo::operator=( + const TransportAvailabilityInfo& other) = default; + +FidoRequestHandlerBase::TransportAvailabilityInfo:: + ~TransportAvailabilityInfo() = default; + +// FidoRequestHandlerBase::TransportAvailabilityObserver ---------------------- + +FidoRequestHandlerBase::TransportAvailabilityObserver:: + ~TransportAvailabilityObserver() = default; + +// FidoRequestHandlerBase ----------------------------------------------------- FidoRequestHandlerBase::FidoRequestHandlerBase( service_manager::Connector* connector, - const base::flat_set<FidoTransportProtocol>& transports, - AddPlatformAuthenticatorCallback add_platform_authenticator) - : add_platform_authenticator_(std::move(add_platform_authenticator)) { - for (const auto transport : transports) { + const base::flat_set<FidoTransportProtocol>& available_transports) + : weak_factory_(this) { + // The number of times |notify_observer_callback_| needs to be invoked before + // Observer::OnTransportAvailabilityEnumerated is dispatched. Essentially this + // is used to wait until all the parts of |transport_availability_info_| are + // filled out; the |notify_observer_callback_| is invoked once for each part + // once that part is ready, namely: + // + // 1) Once the platform authenticator related fields are filled out. + // 2) [Optionally, if BLE or caBLE enabled] if Bluetooth adapter is present. + // + // On top of that, we wait for (3) an invocation that happens when the + // |observer_| is set, so that OnTransportAvailabilityEnumerated is never + // called before the observer is set. + size_t transport_info_callback_count = 1u + 0u + 1u; + + for (const auto transport : available_transports) { // Construction of CaBleDiscovery is handled by the implementing class as it // requires an extension passed on from the relying party. if (transport == FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy) continue; if (transport == FidoTransportProtocol::kInternal) { - // Internal authenticators are injected through - // AddPlatformAuthenticatorCallback. - NOTREACHED(); + // Platform authenticator availability is always indicated by + // |AuthenticatorImpl|. continue; } @@ -46,13 +92,42 @@ FidoRequestHandlerBase::FidoRequestHandlerBase( // HID transports are not configured. continue; } + discovery->set_observer(this); discoveries_.push_back(std::move(discovery)); } + + if (base::ContainsKey( + available_transports, + FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy) || + base::ContainsKey(available_transports, + FidoTransportProtocol::kBluetoothLowEnergy)) { + ++transport_info_callback_count; + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&FidoRequestHandlerBase::ConstructBleAdapterPowerManager, + weak_factory_.GetWeakPtr())); + } + + transport_availability_info_.available_transports = available_transports; + notify_observer_callback_ = base::BarrierClosure( + transport_info_callback_count, + base::BindOnce( + &FidoRequestHandlerBase::NotifyObserverTransportAvailability, + weak_factory_.GetWeakPtr())); } FidoRequestHandlerBase::~FidoRequestHandlerBase() = default; +void FidoRequestHandlerBase::StartAuthenticatorRequest( + const std::string& authenticator_id) { + auto authenticator = active_authenticators_.find(authenticator_id); + if (authenticator == active_authenticators_.end()) + return; + + InitializeAuthenticatorAndDispatchRequest(authenticator->second.get()); +} + void FidoRequestHandlerBase::CancelOngoingTasks( base::StringPiece exclude_device_id) { for (auto task_it = active_authenticators_.begin(); @@ -68,26 +143,45 @@ void FidoRequestHandlerBase::CancelOngoingTasks( } } -void FidoRequestHandlerBase::Start() { - for (const auto& discovery : discoveries_) { - discovery->Start(); +void FidoRequestHandlerBase::OnBluetoothAdapterEnumerated(bool is_present, + bool is_powered_on, + bool can_power_on) { + if (!is_present) { + transport_availability_info_.available_transports.erase( + FidoTransportProtocol::kBluetoothLowEnergy); + transport_availability_info_.available_transports.erase( + FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy); } - MaybeAddPlatformAuthenticator(); + + transport_availability_info_.is_ble_powered = is_powered_on; + transport_availability_info_.can_power_on_ble_adapter = can_power_on; + DCHECK(notify_observer_callback_); + notify_observer_callback_.Run(); } -void FidoRequestHandlerBase::MaybeAddPlatformAuthenticator() { - if (!add_platform_authenticator_) { - return; - } - auto authenticator = std::move(add_platform_authenticator_).Run(); - if (!authenticator) { +void FidoRequestHandlerBase::OnBluetoothAdapterPowerChanged( + bool is_powered_on) { + transport_availability_info_.is_ble_powered = is_powered_on; + + if (observer_) + observer_->BluetoothAdapterPowerChanged(is_powered_on); +} + +void FidoRequestHandlerBase::PowerOnBluetoothAdapter() { + if (!bluetooth_power_manager_) return; - } - AddAuthenticator(std::move(authenticator)); + + bluetooth_power_manager_->SetAdapterPower(true /* set_power_on */); } -void FidoRequestHandlerBase::DiscoveryStarted(FidoDiscovery* discovery, - bool success) {} +base::WeakPtr<FidoRequestHandlerBase> FidoRequestHandlerBase::GetWeakPtr() { + return weak_factory_.GetWeakPtr(); +} + +void FidoRequestHandlerBase::Start() { + for (const auto& discovery : discoveries_) + discovery->Start(); +} void FidoRequestHandlerBase::DeviceAdded(FidoDiscovery* discovery, FidoDevice* device) { @@ -107,16 +201,78 @@ void FidoRequestHandlerBase::DeviceRemoved(FidoDiscovery* discovery, // ongoing_tasks_.erase() will have no effect for the devices that have been // already removed due to processing error or due to invocation of // CancelOngoingTasks(). + DCHECK(device); active_authenticators_.erase(device->GetId()); + + if (observer_) + observer_->FidoAuthenticatorRemoved(device->GetId()); } void FidoRequestHandlerBase::AddAuthenticator( std::unique_ptr<FidoAuthenticator> authenticator) { - DCHECK(!base::ContainsKey(active_authenticators(), authenticator->GetId())); + DCHECK(authenticator && + !base::ContainsKey(active_authenticators(), authenticator->GetId())); FidoAuthenticator* authenticator_ptr = authenticator.get(); active_authenticators_.emplace(authenticator->GetId(), std::move(authenticator)); - DispatchRequest(authenticator_ptr); + + // If |observer_| exists, dispatching request to |authenticator_ptr| is + // delegated to |observer_|. Else, dispatch request to |authenticator_ptr| + // immediately. + bool embedder_controls_dispatch = false; + if (observer_) { + embedder_controls_dispatch = + observer_->EmbedderControlsAuthenticatorDispatch(*authenticator_ptr); + observer_->FidoAuthenticatorAdded(*authenticator_ptr); + } + + if (!embedder_controls_dispatch) { + // Post |InitializeAuthenticatorAndDispatchRequest| into its own task. This + // avoids hairpinning, even if the authenticator immediately invokes the + // request callback. + base::SequencedTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce( + &FidoRequestHandlerBase::InitializeAuthenticatorAndDispatchRequest, + GetWeakPtr(), authenticator_ptr)); + } +} + +void FidoRequestHandlerBase::SetPlatformAuthenticatorOrMarkUnavailable( + base::Optional<PlatformAuthenticatorInfo> platform_authenticator_info) { + if (platform_authenticator_info && + base::ContainsKey(transport_availability_info_.available_transports, + FidoTransportProtocol::kInternal)) { + DCHECK(platform_authenticator_info->authenticator); + DCHECK( + (platform_authenticator_info->authenticator->AuthenticatorTransport() == + FidoTransportProtocol::kInternal)); + transport_availability_info_.has_recognized_mac_touch_id_credential = + platform_authenticator_info->has_recognized_mac_touch_id_credential; + AddAuthenticator(std::move(platform_authenticator_info->authenticator)); + } else { + transport_availability_info_.available_transports.erase( + FidoTransportProtocol::kInternal); + } + + DCHECK(notify_observer_callback_); + notify_observer_callback_.Run(); +} + +void FidoRequestHandlerBase::NotifyObserverTransportAvailability() { + DCHECK(observer_); + observer_->OnTransportAvailabilityEnumerated(transport_availability_info_); +} + +void FidoRequestHandlerBase::InitializeAuthenticatorAndDispatchRequest( + FidoAuthenticator* authenticator) { + authenticator->InitializeAuthenticator( + base::BindOnce(&FidoRequestHandlerBase::DispatchRequest, + weak_factory_.GetWeakPtr(), authenticator)); +} + +void FidoRequestHandlerBase::ConstructBleAdapterPowerManager() { + bluetooth_power_manager_ = std::make_unique<BleAdapterPowerManager>(this); } } // namespace device diff --git a/chromium/device/fido/fido_request_handler_base.h b/chromium/device/fido/fido_request_handler_base.h index 6b9b7e3dc49..aed8c4164a5 100644 --- a/chromium/device/fido/fido_request_handler_base.h +++ b/chromium/device/fido/fido_request_handler_base.h @@ -8,13 +8,16 @@ #include <functional> #include <map> #include <memory> +#include <set> #include <string> #include <vector> #include "base/callback.h" #include "base/component_export.h" #include "base/containers/flat_set.h" +#include "base/logging.h" #include "base/macros.h" +#include "base/memory/weak_ptr.h" #include "base/strings/string_piece_forward.h" #include "device/fido/fido_device_authenticator.h" #include "device/fido/fido_discovery.h" @@ -26,10 +29,22 @@ class Connector; namespace device { +class BleAdapterPowerManager; class FidoAuthenticator; class FidoDevice; class FidoTask; +struct COMPONENT_EXPORT(DEVICE_FIDO) PlatformAuthenticatorInfo { + PlatformAuthenticatorInfo(std::unique_ptr<FidoAuthenticator> authenticator, + bool has_recognized_mac_touch_id_credential); + PlatformAuthenticatorInfo(PlatformAuthenticatorInfo&&); + PlatformAuthenticatorInfo& operator=(PlatformAuthenticatorInfo&& other); + ~PlatformAuthenticatorInfo(); + + std::unique_ptr<FidoAuthenticator> authenticator; + bool has_recognized_mac_touch_id_credential; +}; + // Base class that handles device discovery/removal. Each FidoRequestHandlerBase // is owned by FidoRequestManager and its lifetime is equivalent to that of a // single WebAuthn request. For each authenticator, the per-device work is @@ -40,20 +55,76 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoRequestHandlerBase public: using AuthenticatorMap = std::map<std::string, std::unique_ptr<FidoAuthenticator>, std::less<>>; - using AddPlatformAuthenticatorCallback = - base::OnceCallback<std::unique_ptr<FidoAuthenticator>()>; + using RequestCallback = base::RepeatingCallback<void(const std::string&)>; + + enum class RequestType { kMakeCredential, kGetAssertion }; + + // Encapsulates data required to initiate WebAuthN UX dialog. Once all + // components of TransportAvailabilityInfo is set, + // AuthenticatorRequestClientDelegate should be notified. + // TODO(hongjunchoi): Add async calls to notify embedder when Bluetooth is + // powered on/off. + struct COMPONENT_EXPORT(DEVICE_FIDO) TransportAvailabilityInfo { + TransportAvailabilityInfo(); + TransportAvailabilityInfo(const TransportAvailabilityInfo& other); + TransportAvailabilityInfo& operator=( + const TransportAvailabilityInfo& other); + ~TransportAvailabilityInfo(); + + // TODO(hongjunchoi): Factor |rp_id| and |request_type| from + // TransportAvailabilityInfo. + // See: https://crbug.com/875011 + std::string rp_id; + RequestType request_type = RequestType::kMakeCredential; + + // The intersection of transports supported by the client and allowed by the + // relying party. + base::flat_set<FidoTransportProtocol> available_transports; + + bool has_recognized_mac_touch_id_credential = false; + bool is_ble_powered = false; + bool can_power_on_ble_adapter = false; + }; + + class COMPONENT_EXPORT(DEVICE_FIDO) TransportAvailabilityObserver { + public: + virtual ~TransportAvailabilityObserver(); + + // This method will not be invoked until the observer is set. + virtual void OnTransportAvailabilityEnumerated( + TransportAvailabilityInfo data) = 0; + + // If true, the request handler will defer dispatch of its request onto the + // given authenticator to the embedder. The embedder needs to call + // |StartAuthenticatorRequest| when it wants to initiate request dispatch. + // + // This method is invoked before |FidoAuthenticatorAdded|, and may be + // invoked multiple times for the same authenticator. Depending on the + // result, the request handler might decide not to make the authenticator + // available, in which case it never gets passed to + // |FidoAuthenticatorAdded|. + virtual bool EmbedderControlsAuthenticatorDispatch( + const FidoAuthenticator& authenticator) = 0; + + virtual void BluetoothAdapterPowerChanged(bool is_powered_on) = 0; + virtual void FidoAuthenticatorAdded( + const FidoAuthenticator& authenticator) = 0; + virtual void FidoAuthenticatorRemoved(base::StringPiece device_id) = 0; + }; // TODO(https://crbug.com/769631): Remove the dependency on Connector once - // device/fido is servicified. + // device/fido is servicified. The |available_transports| should be the + // intersection of transports supported by the client and allowed by the + // relying party. FidoRequestHandlerBase( service_manager::Connector* connector, - const base::flat_set<FidoTransportProtocol>& transports); - FidoRequestHandlerBase( - service_manager::Connector* connector, - const base::flat_set<FidoTransportProtocol>& transports, - AddPlatformAuthenticatorCallback add_platform_authenticator); + const base::flat_set<FidoTransportProtocol>& available_transports); ~FidoRequestHandlerBase() override; + // Triggers DispatchRequest() if |active_authenticators_| hold + // FidoAuthenticator with given |authenticator_id|. + void StartAuthenticatorRequest(const std::string& authenticator_id); + // Triggers cancellation of all per-device FidoTasks, except for the device // with |exclude_device_id|, if one is provided. Cancelled tasks are // immediately removed from |ongoing_tasks_|. @@ -65,6 +136,31 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoRequestHandlerBase // per-device tasks are cancelled. // https://w3c.github.io/webauthn/#iface-pkcredential void CancelOngoingTasks(base::StringPiece exclude_device_id = nullptr); + void OnBluetoothAdapterEnumerated(bool is_present, + bool is_powered_on, + bool can_power_on); + void OnBluetoothAdapterPowerChanged(bool is_powered_on); + void PowerOnBluetoothAdapter(); + + base::WeakPtr<FidoRequestHandlerBase> GetWeakPtr(); + + void set_observer(TransportAvailabilityObserver* observer) { + DCHECK(!observer_) << "Only one observer is supported."; + observer_ = observer; + + DCHECK(notify_observer_callback_); + notify_observer_callback_.Run(); + } + + // Set the platform authenticator for this request, if one is available. + // |AuthenticatorImpl| must call this method after invoking |set_oberver| even + // if no platform authenticator is available, in which case it passes nullptr. + virtual void SetPlatformAuthenticatorOrMarkUnavailable( + base::Optional<PlatformAuthenticatorInfo> platform_authenticator_info); + + TransportAvailabilityInfo& transport_availability_info() { + return transport_availability_info_; + } protected: // Subclasses implement this method to dispatch their request onto the given @@ -82,21 +178,31 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoRequestHandlerBase std::vector<std::unique_ptr<FidoDiscovery>>& discoveries() { return discoveries_; } + TransportAvailabilityObserver* observer() const { return observer_; } private: // FidoDiscovery::Observer - void DiscoveryStarted(FidoDiscovery* discovery, bool success) final; void DeviceAdded(FidoDiscovery* discovery, FidoDevice* device) final; void DeviceRemoved(FidoDiscovery* discovery, FidoDevice* device) final; void AddAuthenticator(std::unique_ptr<FidoAuthenticator> authenticator); + void NotifyObserverTransportAvailability(); - void MaybeAddPlatformAuthenticator(); + // Invokes FidoAuthenticator::InitializeAuthenticator(), followed by + // DispatchRequest(). InitializeAuthenticator() sends a GetInfo command + // to FidoDeviceAuthenticator instances in order to determine their protocol + // versions before a request can be dispatched. + void InitializeAuthenticatorAndDispatchRequest(FidoAuthenticator*); + void ConstructBleAdapterPowerManager(); AuthenticatorMap active_authenticators_; std::vector<std::unique_ptr<FidoDiscovery>> discoveries_; + TransportAvailabilityObserver* observer_ = nullptr; + TransportAvailabilityInfo transport_availability_info_; + base::RepeatingClosure notify_observer_callback_; + std::unique_ptr<BleAdapterPowerManager> bluetooth_power_manager_; - AddPlatformAuthenticatorCallback add_platform_authenticator_; + base::WeakPtrFactory<FidoRequestHandlerBase> weak_factory_; DISALLOW_COPY_AND_ASSIGN(FidoRequestHandlerBase); }; diff --git a/chromium/device/fido/fido_request_handler_unittest.cc b/chromium/device/fido/fido_request_handler_unittest.cc index c497d0a7f2c..d617fb1d11b 100644 --- a/chromium/device/fido/fido_request_handler_unittest.cc +++ b/chromium/device/fido/fido_request_handler_unittest.cc @@ -9,6 +9,8 @@ #include "base/bind.h" #include "base/numerics/safe_conversions.h" #include "base/test/scoped_task_environment.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" +#include "device/bluetooth/test/mock_bluetooth_adapter.h" #include "device/fido/fake_fido_discovery.h" #include "device/fido/fido_constants.h" #include "device/fido/fido_device.h" @@ -30,17 +32,68 @@ namespace { using FakeTaskCallback = base::OnceCallback<void(CtapDeviceResponseCode status_code, base::Optional<std::vector<uint8_t>>)>; -using FakeHandlerCallback = base::OnceCallback<void( - FidoReturnCode status_code, - base::Optional<std::vector<uint8_t>> response_data)>; +using FakeHandlerCallback = + base::OnceCallback<void(FidoReturnCode status_code, + base::Optional<std::vector<uint8_t>> response_data, + FidoTransportProtocol)>; using FakeHandlerCallbackReceiver = - test::StatusAndValueCallbackReceiver<FidoReturnCode, - base::Optional<std::vector<uint8_t>>>; + test::StatusAndValuesCallbackReceiver<FidoReturnCode, + base::Optional<std::vector<uint8_t>>, + FidoTransportProtocol>; enum class FakeTaskResponse : uint8_t { kSuccess = 0x00, kErrorReceivedAfterObtainingUserPresence = 0x01, kProcessingError = 0x02, + kOperationDenied = 0x03, +}; + +class TestTransportAvailabilityObserver + : public FidoRequestHandlerBase::TransportAvailabilityObserver { + public: + using TransportAvailabilityNotificationReceiver = test::TestCallbackReceiver< + FidoRequestHandlerBase::TransportAvailabilityInfo>; + + TestTransportAvailabilityObserver() {} + ~TestTransportAvailabilityObserver() override {} + + void WaitForAndExpectAvailableTransportsAre( + base::flat_set<FidoTransportProtocol> expected_transports, + base::Optional<bool> has_recognized_mac_touch_id_credential = + base::nullopt) { + transport_availability_notification_receiver_.WaitForCallback(); + auto result = + std::get<0>(*transport_availability_notification_receiver_.result()); + EXPECT_THAT(result.available_transports, + ::testing::UnorderedElementsAreArray(expected_transports)); + if (has_recognized_mac_touch_id_credential) { + EXPECT_EQ(*has_recognized_mac_touch_id_credential, + result.has_recognized_mac_touch_id_credential); + } + } + + protected: + // FidoRequestHandlerBase::TransportAvailabilityObserver: + void OnTransportAvailabilityEnumerated( + FidoRequestHandlerBase::TransportAvailabilityInfo data) override { + transport_availability_notification_receiver_.callback().Run( + std::move(data)); + } + bool EmbedderControlsAuthenticatorDispatch( + const FidoAuthenticator&) override { + return false; + } + + void BluetoothAdapterPowerChanged(bool is_powered_on) override {} + void FidoAuthenticatorAdded(const FidoAuthenticator& authenticator) override { + } + void FidoAuthenticatorRemoved(base::StringPiece device_id) override {} + + private: + TransportAvailabilityNotificationReceiver + transport_availability_notification_receiver_; + + DISALLOW_COPY_AND_ASSIGN(TestTransportAvailabilityObserver); }; // Fake FidoTask implementation that sends an empty byte array to the device @@ -71,6 +124,10 @@ class FakeFidoTask : public FidoTask { std::vector<uint8_t>()); return; + case FakeTaskResponse::kOperationDenied: + std::move(callback_).Run( + CtapDeviceResponseCode::kCtap2ErrOperationDenied, base::nullopt); + return; case FakeTaskResponse::kProcessingError: default: std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther, @@ -86,7 +143,8 @@ class FakeFidoTask : public FidoTask { class FakeFidoAuthenticator : public FidoDeviceAuthenticator { public: - FakeFidoAuthenticator(FidoDevice* device) : FidoDeviceAuthenticator(device) {} + explicit FakeFidoAuthenticator(FidoDevice* device) + : FidoDeviceAuthenticator(device) {} void RunFakeTask(FakeTaskCallback callback) { SetTaskForTesting( @@ -96,15 +154,11 @@ class FakeFidoAuthenticator : public FidoDeviceAuthenticator { class FakeFidoRequestHandler : public FidoRequestHandler<std::vector<uint8_t>> { public: - FakeFidoRequestHandler( - const base::flat_set<FidoTransportProtocol>& protocols, - FakeHandlerCallback callback, - AddPlatformAuthenticatorCallback add_platform_authenticator = - AddPlatformAuthenticatorCallback()) + FakeFidoRequestHandler(const base::flat_set<FidoTransportProtocol>& protocols, + FakeHandlerCallback callback) : FidoRequestHandler(nullptr /* connector */, protocols, - std::move(callback), - std::move(add_platform_authenticator)), + std::move(callback)), weak_factory_(this) { Start(); } @@ -139,39 +193,50 @@ std::vector<uint8_t> CreateFakeDeviceProcesssingError() { return {base::strict_cast<uint8_t>(FakeTaskResponse::kProcessingError)}; } +std::vector<uint8_t> CreateFakeOperationDeniedError() { + return {base::strict_cast<uint8_t>(FakeTaskResponse::kOperationDenied)}; +} + } // namespace class FidoRequestHandlerTest : public ::testing::Test { public: + FidoRequestHandlerTest() { + mock_adapter_ = + base::MakeRefCounted<::testing::NiceMock<MockBluetoothAdapter>>(); + BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter_); + } + void ForgeNextHidDiscovery() { discovery_ = scoped_fake_discovery_factory_.ForgeNextHidDiscovery(); + ble_discovery_ = scoped_fake_discovery_factory_.ForgeNextBleDiscovery(); } std::unique_ptr<FakeFidoRequestHandler> CreateFakeHandler() { ForgeNextHidDiscovery(); - return std::make_unique<FakeFidoRequestHandler>( + auto handler = std::make_unique<FakeFidoRequestHandler>( base::flat_set<FidoTransportProtocol>( - {FidoTransportProtocol::kUsbHumanInterfaceDevice}), + {FidoTransportProtocol::kUsbHumanInterfaceDevice, + FidoTransportProtocol::kBluetoothLowEnergy}), cb_.callback()); - } - - std::unique_ptr<FakeFidoRequestHandler> - CreateFakeHandlerWithPlatformAuthenticatorCallback( - FidoRequestHandlerBase::AddPlatformAuthenticatorCallback - add_platform_authenticator) { - return std::make_unique<FakeFidoRequestHandler>( - base::flat_set<FidoTransportProtocol>(), cb_.callback(), - std::move(add_platform_authenticator)); + handler->SetPlatformAuthenticatorOrMarkUnavailable(base::nullopt); + return handler; } test::FakeFidoDiscovery* discovery() const { return discovery_; } + test::FakeFidoDiscovery* ble_discovery() const { return ble_discovery_; } + scoped_refptr<::testing::NiceMock<MockBluetoothAdapter>> adapter() { + return mock_adapter_; + } FakeHandlerCallbackReceiver& callback() { return cb_; } protected: base::test::ScopedTaskEnvironment scoped_task_environment_{ base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME}; test::ScopedFakeFidoDiscoveryFactory scoped_fake_discovery_factory_; + scoped_refptr<::testing::NiceMock<MockBluetoothAdapter>> mock_adapter_; test::FakeFidoDiscovery* discovery_; + test::FakeFidoDiscovery* ble_discovery_; FakeHandlerCallbackReceiver cb_; }; @@ -222,8 +287,8 @@ TEST_F(FidoRequestHandlerTest, TestAuthenticatorHandlerReset) { request_handler.reset(); } -// Test a scenario where 2 devices are connected and a response is received from -// only a single device(device1) and the remaining device hangs. +// Test a scenario where 2 devices are connected and a response is received +// from only a single device(device1) and the remaining device hangs. TEST_F(FidoRequestHandlerTest, TestRequestWithMultipleDevices) { auto request_handler = CreateFakeHandler(); discovery()->WaitForCallToStartAndSimulateSuccess(); @@ -296,11 +361,11 @@ TEST_F(FidoRequestHandlerTest, TestRequestWithMultipleSuccessResponses) { } // Test a scenario where 3 devices respond with a processing error, an UP(user -// presence) verified failure response with small time delay, and an UP verified -// failure response with big time delay, respectively. Request for device with -// processing error should be immediately dropped. Also, for UP verified -// failures, the first received response should be passed on to the relying -// party and cancel command should be sent to the remaining device. +// presence) verified failure response with small time delay, and an UP +// verified failure response with big time delay, respectively. Request for +// device with processing error should be immediately dropped. Also, for UP +// verified failures, the first received response should be passed on to the +// relying party and cancel command should be sent to the remaining device. TEST_F(FidoRequestHandlerTest, TestRequestWithMultipleFailureResponses) { auto request_handler = CreateFakeHandler(); discovery()->WaitForCallToStartAndSimulateSuccess(); @@ -349,31 +414,177 @@ TEST_F(FidoRequestHandlerTest, TestRequestWithMultipleFailureResponses) { callback().status()); } -// Requests should be dispatched to the authenticator returned from the -// AddPlatformAuthenticatorCallback if one is passed. -TEST_F(FidoRequestHandlerTest, TestPlatformAuthenticatorCallback) { +// If a device with transport type kInternal returns a +// CTAP2_ERR_OPERATION_DENIED error, the request should be cancelled on all +// pending authenticators. +TEST_F(FidoRequestHandlerTest, + TestRequestWithOperationDeniedErrorInternalTransport) { + auto request_handler = CreateFakeHandler(); + discovery()->WaitForCallToStartAndSimulateSuccess(); + + // Device will send CTAP2_ERR_OPERATION_DENIED. + auto device0 = MockFidoDevice::MakeCtapWithGetInfoExpectation( + test_data::kTestGetInfoResponsePlatformDevice); + device0->SetDeviceTransport(FidoTransportProtocol::kInternal); + device0->ExpectRequestAndRespondWith(std::vector<uint8_t>(), + CreateFakeOperationDeniedError(), + base::TimeDelta::FromMicroseconds(10)); + + auto device1 = MockFidoDevice::MakeCtapWithGetInfoExpectation(); + device1->ExpectRequestAndDoNotRespond(std::vector<uint8_t>()); + EXPECT_CALL(*device1, Cancel()); + + discovery()->AddDevice(std::move(device0)); + discovery()->AddDevice(std::move(device1)); + + scoped_task_environment_.FastForwardUntilNoTasksRemain(); + callback().WaitForCallback(); + EXPECT_TRUE(request_handler->is_complete()); + EXPECT_EQ(FidoReturnCode::kUserConsentDenied, callback().status()); +} + +// Like |TestRequestWithOperationDeniedErrorInternalTransport|, but with a +// cross-platform authenticator. +TEST_F(FidoRequestHandlerTest, + TestRequestWithOperationDeniedErrorCrossPlatform) { + auto request_handler = CreateFakeHandler(); + discovery()->WaitForCallToStartAndSimulateSuccess(); + + // Device will send CTAP2_ERR_OPERATION_DENIED. + auto device0 = MockFidoDevice::MakeCtapWithGetInfoExpectation(); + device0->SetDeviceTransport(FidoTransportProtocol::kUsbHumanInterfaceDevice); + device0->ExpectRequestAndRespondWith(std::vector<uint8_t>(), + CreateFakeOperationDeniedError()); + + auto device1 = MockFidoDevice::MakeCtapWithGetInfoExpectation(); + device1->ExpectRequestAndDoNotRespond(std::vector<uint8_t>()); + EXPECT_CALL(*device1, Cancel()); + + discovery()->AddDevice(std::move(device0)); + discovery()->AddDevice(std::move(device1)); + + scoped_task_environment_.FastForwardUntilNoTasksRemain(); + callback().WaitForCallback(); + EXPECT_TRUE(request_handler->is_complete()); + EXPECT_EQ(FidoReturnCode::kUserConsentDenied, callback().status()); +} + +// Requests should be dispatched to the authenticator passed to +// SetPlatformAuthenticatorOrMarkUnavailable. +TEST_F(FidoRequestHandlerTest, TestSetPlatformAuthenticator) { // A platform authenticator usually wouldn't usually use a FidoDevice, but // that's not the point of the test here. The test is only trying to ensure // the authenticator gets injected and used. auto device = MockFidoDevice::MakeCtap(); EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0")); + device->ExpectCtap2CommandAndRespondWith( + CtapRequestCommand::kAuthenticatorGetInfo, + test_data::kTestGetInfoResponsePlatformDevice); // Device returns success response. device->ExpectRequestAndRespondWith(std::vector<uint8_t>(), CreateFakeSuccessDeviceResponse()); + device->SetDeviceTransport(FidoTransportProtocol::kInternal); + auto authenticator = std::make_unique<FakeFidoAuthenticator>(device.get()); - FidoRequestHandlerBase::AddPlatformAuthenticatorCallback - make_platform_authenticator = base::BindOnce( - [](FidoDevice* device) -> std::unique_ptr<FidoAuthenticator> { - return std::make_unique<FakeFidoAuthenticator>(device); - }, - device.get()); - auto request_handler = CreateFakeHandlerWithPlatformAuthenticatorCallback( - std::move(make_platform_authenticator)); + TestTransportAvailabilityObserver observer; + auto request_handler = std::make_unique<FakeFidoRequestHandler>( + base::flat_set<FidoTransportProtocol>({FidoTransportProtocol::kInternal}), + callback().callback()); + request_handler->set_observer(&observer); + request_handler->SetPlatformAuthenticatorOrMarkUnavailable( + PlatformAuthenticatorInfo(std::move(authenticator), false)); + + observer.WaitForAndExpectAvailableTransportsAre( + {FidoTransportProtocol::kInternal}, + false /* has_recognized_mac_touch_id_credential */); + + callback().WaitForCallback(); + EXPECT_TRUE(request_handler->is_complete()); + EXPECT_EQ(FidoReturnCode::kSuccess, callback().status()); +} + +// SetPlatformAuthenticatorOrMarkUnavailable should propagate the +// has_recognized_mac_touch_id_credential field. +TEST_F(FidoRequestHandlerTest, + TestSetPlatformAuthenticatorHasTouchIdCredential) { + // A platform authenticator usually wouldn't usually use a FidoDevice, but + // that's not the point of the test here. The test is only trying to ensure + // the authenticator gets injected and used. + auto device = MockFidoDevice::MakeCtap(); + EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0")); + // Device returns success response. + device->ExpectCtap2CommandAndRespondWith( + CtapRequestCommand::kAuthenticatorGetInfo, + test_data::kTestAuthenticatorGetInfoResponse); + device->ExpectRequestAndRespondWith(std::vector<uint8_t>(), + CreateFakeSuccessDeviceResponse()); + device->SetDeviceTransport(FidoTransportProtocol::kInternal); + auto authenticator = std::make_unique<FakeFidoAuthenticator>(device.get()); + + TestTransportAvailabilityObserver observer; + auto request_handler = std::make_unique<FakeFidoRequestHandler>( + base::flat_set<FidoTransportProtocol>({FidoTransportProtocol::kInternal}), + callback().callback()); + request_handler->set_observer(&observer); + request_handler->SetPlatformAuthenticatorOrMarkUnavailable( + PlatformAuthenticatorInfo(std::move(authenticator), true)); + + observer.WaitForAndExpectAvailableTransportsAre( + {FidoTransportProtocol::kInternal}, + true /* has_recognized_mac_touch_id_credential */); - scoped_task_environment_.FastForwardUntilNoTasksRemain(); callback().WaitForCallback(); EXPECT_TRUE(request_handler->is_complete()); EXPECT_EQ(FidoReturnCode::kSuccess, callback().status()); } +TEST_F(FidoRequestHandlerTest, InternalTransportDisallowedIfMarkedUnavailable) { + TestTransportAvailabilityObserver observer; + auto request_handler = std::make_unique<FakeFidoRequestHandler>( + base::flat_set<FidoTransportProtocol>({FidoTransportProtocol::kInternal}), + callback().callback()); + request_handler->set_observer(&observer); + request_handler->SetPlatformAuthenticatorOrMarkUnavailable(base::nullopt); + + observer.WaitForAndExpectAvailableTransportsAre({}); +} + +TEST_F(FidoRequestHandlerTest, BleTransportAllowedIfBluetoothAdapterPresent) { + EXPECT_CALL(*adapter(), IsPresent()).WillOnce(::testing::Return(true)); + + TestTransportAvailabilityObserver observer; + auto request_handler = CreateFakeHandler(); + request_handler->set_observer(&observer); + + observer.WaitForAndExpectAvailableTransportsAre( + {FidoTransportProtocol::kUsbHumanInterfaceDevice, + FidoTransportProtocol::kBluetoothLowEnergy}); +} + +TEST_F(FidoRequestHandlerTest, + BleTransportDisallowedBluetoothAdapterNotPresent) { + EXPECT_CALL(*adapter(), IsPresent()).WillOnce(::testing::Return(false)); + + TestTransportAvailabilityObserver observer; + auto request_handler = CreateFakeHandler(); + request_handler->set_observer(&observer); + + observer.WaitForAndExpectAvailableTransportsAre( + {FidoTransportProtocol::kUsbHumanInterfaceDevice}); +} + +TEST_F(FidoRequestHandlerTest, + TransportAvailabilityNotificationOnObserverSetLate) { + EXPECT_CALL(*adapter(), IsPresent()).WillOnce(::testing::Return(true)); + + TestTransportAvailabilityObserver observer; + auto request_handler = CreateFakeHandler(); + scoped_task_environment_.FastForwardUntilNoTasksRemain(); + + request_handler->set_observer(&observer); + observer.WaitForAndExpectAvailableTransportsAre( + {FidoTransportProtocol::kUsbHumanInterfaceDevice, + FidoTransportProtocol::kBluetoothLowEnergy}); +} + } // namespace device diff --git a/chromium/device/fido/fido_strings.grd b/chromium/device/fido/fido_strings.grd new file mode 100644 index 00000000000..8eaa3873834 --- /dev/null +++ b/chromium/device/fido/fido_strings.grd @@ -0,0 +1,136 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- +This file contains the strings for //device/fido. +--> + +<grit base_dir="." latest_public_release="0" current_release="1" + output_all_resource_defines="false" source_lang_id="en" enc_check="möl"> + <outputs> + <output filename="grit/fido_strings.h" type="rc_header"> + <emit emit_type='prepend'></emit> + </output> + <output filename="fido_strings_am.pak" type="data_package" lang="am" /> + <output filename="fido_strings_ar.pak" type="data_package" lang="ar" /> + <output filename="fido_strings_bg.pak" type="data_package" lang="bg" /> + <output filename="fido_strings_bn.pak" type="data_package" lang="bn" /> + <output filename="fido_strings_ca.pak" type="data_package" lang="ca" /> + <output filename="fido_strings_cs.pak" type="data_package" lang="cs" /> + <output filename="fido_strings_da.pak" type="data_package" lang="da" /> + <output filename="fido_strings_de.pak" type="data_package" lang="de" /> + <output filename="fido_strings_el.pak" type="data_package" lang="el" /> + <output filename="fido_strings_en-GB.pak" type="data_package" lang="en-GB" /> + <output filename="fido_strings_en-US.pak" type="data_package" lang="en" /> + <output filename="fido_strings_es.pak" type="data_package" lang="es" /> + <output filename="fido_strings_es-419.pak" type="data_package" lang="es-419" /> + <output filename="fido_strings_et.pak" type="data_package" lang="et" /> + <output filename="fido_strings_fa.pak" type="data_package" lang="fa" /> + <output filename="fido_strings_fake-bidi.pak" type="data_package" lang="fake-bidi" /> + <output filename="fido_strings_fi.pak" type="data_package" lang="fi" /> + <output filename="fido_strings_fil.pak" type="data_package" lang="fil" /> + <output filename="fido_strings_fr.pak" type="data_package" lang="fr" /> + <output filename="fido_strings_gu.pak" type="data_package" lang="gu" /> + <output filename="fido_strings_he.pak" type="data_package" lang="he" /> + <output filename="fido_strings_hi.pak" type="data_package" lang="hi" /> + <output filename="fido_strings_hr.pak" type="data_package" lang="hr" /> + <output filename="fido_strings_hu.pak" type="data_package" lang="hu" /> + <output filename="fido_strings_id.pak" type="data_package" lang="id" /> + <output filename="fido_strings_it.pak" type="data_package" lang="it" /> + <output filename="fido_strings_ja.pak" type="data_package" lang="ja" /> + <output filename="fido_strings_kn.pak" type="data_package" lang="kn" /> + <output filename="fido_strings_ko.pak" type="data_package" lang="ko" /> + <output filename="fido_strings_lt.pak" type="data_package" lang="lt" /> + <output filename="fido_strings_lv.pak" type="data_package" lang="lv" /> + <output filename="fido_strings_ml.pak" type="data_package" lang="ml" /> + <output filename="fido_strings_mr.pak" type="data_package" lang="mr" /> + <output filename="fido_strings_ms.pak" type="data_package" lang="ms" /> + <output filename="fido_strings_nl.pak" type="data_package" lang="nl" /> + <!-- The translation console uses 'no' for Norwegian Bokmål. It should + be 'nb'. --> + <output filename="fido_strings_nb.pak" type="data_package" lang="no" /> + <output filename="fido_strings_pl.pak" type="data_package" lang="pl" /> + <output filename="fido_strings_pt-BR.pak" type="data_package" lang="pt-BR" /> + <output filename="fido_strings_pt-PT.pak" type="data_package" lang="pt-PT" /> + <output filename="fido_strings_ro.pak" type="data_package" lang="ro" /> + <output filename="fido_strings_ru.pak" type="data_package" lang="ru" /> + <output filename="fido_strings_sk.pak" type="data_package" lang="sk" /> + <output filename="fido_strings_sl.pak" type="data_package" lang="sl" /> + <output filename="fido_strings_sr.pak" type="data_package" lang="sr" /> + <output filename="fido_strings_sv.pak" type="data_package" lang="sv" /> + <output filename="fido_strings_sw.pak" type="data_package" lang="sw" /> + <output filename="fido_strings_ta.pak" type="data_package" lang="ta" /> + <output filename="fido_strings_te.pak" type="data_package" lang="te" /> + <output filename="fido_strings_th.pak" type="data_package" lang="th" /> + <output filename="fido_strings_tr.pak" type="data_package" lang="tr" /> + <output filename="fido_strings_uk.pak" type="data_package" lang="uk" /> + <output filename="fido_strings_vi.pak" type="data_package" lang="vi" /> + <output filename="fido_strings_zh-CN.pak" type="data_package" lang="zh-CN" /> + <output filename="fido_strings_zh-TW.pak" type="data_package" lang="zh-TW" /> + </outputs> + <translations> + <file path="strings/fido_strings_am.xtb" lang="am" /> + <file path="strings/fido_strings_ar.xtb" lang="ar" /> + <file path="strings/fido_strings_bg.xtb" lang="bg" /> + <file path="strings/fido_strings_bn.xtb" lang="bn" /> + <file path="strings/fido_strings_ca.xtb" lang="ca" /> + <file path="strings/fido_strings_cs.xtb" lang="cs" /> + <file path="strings/fido_strings_da.xtb" lang="da" /> + <file path="strings/fido_strings_de.xtb" lang="de" /> + <file path="strings/fido_strings_el.xtb" lang="el" /> + <file path="strings/fido_strings_en-GB.xtb" lang="en-GB" /> + <file path="strings/fido_strings_es.xtb" lang="es" /> + <file path="strings/fido_strings_es-419.xtb" lang="es-419" /> + <file path="strings/fido_strings_et.xtb" lang="et" /> + <file path="strings/fido_strings_fa.xtb" lang="fa" /> + <file path="strings/fido_strings_fi.xtb" lang="fi" /> + <file path="strings/fido_strings_fil.xtb" lang="fil" /> + <file path="strings/fido_strings_fr.xtb" lang="fr" /> + <file path="strings/fido_strings_gu.xtb" lang="gu" /> + <file path="strings/fido_strings_hi.xtb" lang="hi" /> + <file path="strings/fido_strings_hr.xtb" lang="hr" /> + <file path="strings/fido_strings_hu.xtb" lang="hu" /> + <file path="strings/fido_strings_id.xtb" lang="id" /> + <file path="strings/fido_strings_it.xtb" lang="it" /> + <!-- The translation console uses 'iw' for Hebrew, but we use 'he'. --> + <file path="strings/fido_strings_iw.xtb" lang="he" /> + <file path="strings/fido_strings_ja.xtb" lang="ja" /> + <file path="strings/fido_strings_kn.xtb" lang="kn" /> + <file path="strings/fido_strings_ko.xtb" lang="ko" /> + <file path="strings/fido_strings_lt.xtb" lang="lt" /> + <file path="strings/fido_strings_lv.xtb" lang="lv" /> + <file path="strings/fido_strings_ml.xtb" lang="ml" /> + <file path="strings/fido_strings_mr.xtb" lang="mr" /> + <file path="strings/fido_strings_ms.xtb" lang="ms" /> + <file path="strings/fido_strings_nl.xtb" lang="nl" /> + <file path="strings/fido_strings_no.xtb" lang="no" /> + <file path="strings/fido_strings_pl.xtb" lang="pl" /> + <file path="strings/fido_strings_pt-BR.xtb" lang="pt-BR" /> + <file path="strings/fido_strings_pt-PT.xtb" lang="pt-PT" /> + <file path="strings/fido_strings_ro.xtb" lang="ro" /> + <file path="strings/fido_strings_ru.xtb" lang="ru" /> + <file path="strings/fido_strings_sk.xtb" lang="sk" /> + <file path="strings/fido_strings_sl.xtb" lang="sl" /> + <file path="strings/fido_strings_sr.xtb" lang="sr" /> + <file path="strings/fido_strings_sv.xtb" lang="sv" /> + <file path="strings/fido_strings_sw.xtb" lang="sw" /> + <file path="strings/fido_strings_ta.xtb" lang="ta" /> + <file path="strings/fido_strings_te.xtb" lang="te" /> + <file path="strings/fido_strings_th.xtb" lang="th" /> + <file path="strings/fido_strings_tr.xtb" lang="tr" /> + <file path="strings/fido_strings_uk.xtb" lang="uk" /> + <file path="strings/fido_strings_vi.xtb" lang="vi" /> + <file path="strings/fido_strings_zh-CN.xtb" lang="zh-CN" /> + <file path="strings/fido_strings_zh-TW.xtb" lang="zh-TW" /> + </translations> + <release seq="1" allow_pseudo="false"> + <messages fallback_to_english="true"> + <if expr="is_macosx"> + <message name="IDS_WEBAUTHN_TOUCH_ID_PROMPT_REASON" desc="Sentence fragment (lower case). Shown after 'Google Chrome is trying to ' (or in some languages, 'Google Chrome wants to ') in a dialog message asking the user to confirm or cancel this action."> + verify your identity on <ph name="APP_NAME">$1<ex>google.com</ex></ph> + </message> + </if> + </messages> + </release> +</grit> + + diff --git a/chromium/device/fido/fido_task.h b/chromium/device/fido/fido_task.h index b2ca94b87bf..ab4c8ad786a 100644 --- a/chromium/device/fido/fido_task.h +++ b/chromium/device/fido/fido_task.h @@ -18,13 +18,9 @@ namespace device { // Encapsulates per-device request logic shared between MakeCredential and -// GetAssertion. Handles issuing the AuthenticatorGetInfo command to tokens, -// caching device info, and distinguishing U2F tokens from CTAP tokens. +// GetAssertion. // -// FidoTask is owned by FidoRequestHandler and manages all interaction with -// |device_|. It is created when a new device is discovered by FidoDiscovery and -// destroyed when the device is removed or when a successful response has been -// issued to the relying party from another authenticator. +// TODO(martinkr): FidoTask should be subsumed by FidoDeviceAuthenticator. class COMPONENT_EXPORT(DEVICE_FIDO) FidoTask { public: // The |device| must outlive the FidoTask instance. diff --git a/chromium/device/fido/fido_test_data.h b/chromium/device/fido/fido_test_data.h index e36d0da259f..88673b1e8b7 100644 --- a/chromium/device/fido/fido_test_data.h +++ b/chromium/device/fido/fido_test_data.h @@ -918,7 +918,7 @@ constexpr uint8_t kCtapMakeCredentialRequest[] = { // True(21) 0xf5}; -constexpr uint8_t kCtapGetAssertionRequest[] = { +constexpr uint8_t kTestComplexCtapGetAssertionRequest[] = { // authenticatorGetAssertion command 0x02, // map(4) @@ -977,12 +977,93 @@ constexpr uint8_t kCtapGetAssertionRequest[] = { // key - "uv" 0x62, 0x75, 0x76, // value - True(21) - 0xf5}; + 0xf5, +}; + +constexpr uint8_t kCtapGetAssertionRequest[] = { + // authenticatorGetAssertion command + 0x02, + // map(3) + 0xa3, + // key(01) -rpId + 0x01, + // value - "acme.com" + 0x68, 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + // key(02) - client data hash + 0x02, + // value - bytes(32) + 0x58, 0x20, 0x68, 0x71, 0x34, 0x96, 0x82, 0x22, 0xec, 0x17, 0x20, 0x2e, + 0x42, 0x50, 0x5f, 0x8e, 0xd2, 0xb1, 0x6a, 0xe2, 0x2f, 0x16, 0xbb, 0x05, + 0xb8, 0x8c, 0x25, 0xdb, 0x9e, 0x60, 0x26, 0x45, 0xf1, 0x41, + // key(03) - allow list + 0x03, + // value - array(1) + 0x81, + // map(2) + 0xa2, + // key - "id" + 0x62, 0x69, 0x64, + // value - credential ID + 0x58, 0x40, 0x3E, 0xBD, 0x89, 0xBF, 0x77, 0xEC, 0x50, 0x97, 0x55, 0xEE, + 0x9C, 0x26, 0x35, 0xEF, 0xAA, 0xAC, 0x7B, 0x2B, 0x9C, 0x5C, 0xEF, 0x17, + 0x36, 0xC3, 0x71, 0x7D, 0xA4, 0x85, 0x34, 0xC8, 0xC6, 0xB6, 0x54, 0xD7, + 0xFF, 0x94, 0x5F, 0x50, 0xB5, 0xCC, 0x4E, 0x78, 0x05, 0x5B, 0xDD, 0x39, + 0x6B, 0x64, 0xF7, 0x8D, 0xA2, 0xC5, 0xF9, 0x62, 0x00, 0xCC, 0xD4, 0x15, + 0xCD, 0x08, 0xFE, 0x42, 0x00, 0x38, + // key - "type" + 0x64, 0x74, 0x79, 0x70, 0x65, + // value - "public-key" + 0x6a, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79, +}; + +constexpr uint8_t kCtapSilentGetAssertionRequest[] = { + // authenticatorGetAssertion command + 0x02, + // map(4) + 0xa4, + // key(01) -rpId + 0x01, + // value - "acme.com" + 0x68, 0x61, 0x63, 0x6d, 0x65, 0x2e, 0x63, 0x6f, 0x6d, + // key(02) - client data hash + 0x02, + // value - bytes(32) + 0x58, 0x20, 0x68, 0x71, 0x34, 0x96, 0x82, 0x22, 0xec, 0x17, 0x20, 0x2e, + 0x42, 0x50, 0x5f, 0x8e, 0xd2, 0xb1, 0x6a, 0xe2, 0x2f, 0x16, 0xbb, 0x05, + 0xb8, 0x8c, 0x25, 0xdb, 0x9e, 0x60, 0x26, 0x45, 0xf1, 0x41, + // key(03) - allow list + 0x03, + // value - array(1) + 0x81, + // map(2) + 0xa2, + // key - "id" + 0x62, 0x69, 0x64, + // value - credential ID + 0x58, 0x40, 0x3E, 0xBD, 0x89, 0xBF, 0x77, 0xEC, 0x50, 0x97, 0x55, 0xEE, + 0x9C, 0x26, 0x35, 0xEF, 0xAA, 0xAC, 0x7B, 0x2B, 0x9C, 0x5C, 0xEF, 0x17, + 0x36, 0xC3, 0x71, 0x7D, 0xA4, 0x85, 0x34, 0xC8, 0xC6, 0xB6, 0x54, 0xD7, + 0xFF, 0x94, 0x5F, 0x50, 0xB5, 0xCC, 0x4E, 0x78, 0x05, 0x5B, 0xDD, 0x39, + 0x6B, 0x64, 0xF7, 0x8D, 0xA2, 0xC5, 0xF9, 0x62, 0x00, 0xCC, 0xD4, 0x15, + 0xCD, 0x08, 0xFE, 0x42, 0x00, 0x38, + // key - "type" + 0x64, 0x74, 0x79, 0x70, 0x65, + // value - "public-key" + 0x6a, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79, + // unsigned(5) - options + 0x05, + // map(1) + 0xa1, + // key -"up" + 0x62, 0x75, 0x70, + // value - False(20) + 0xf4, +}; // CTAP responses -------------------------------------------------------------- -// A sample well formed response to CTAP AuthenticatorGetInfo request. Supports -// platform device, resident key, and user verification. +// A sample well formed response to CTAP AuthenticatorGetInfo request. Cross +// platform device that supports resident key, and user verification. constexpr uint8_t kTestAuthenticatorGetInfoResponse[] = { 0x00, 0xA6, 0x01, 0x82, 0x68, 0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x30, 0x66, 0x55, 0x32, 0x46, 0x5F, 0x56, 0x32, 0x02, 0x82, 0x63, 0x75, @@ -990,11 +1071,24 @@ constexpr uint8_t kTestAuthenticatorGetInfoResponse[] = { 0x65, 0x74, 0x03, 0x50, 0xF8, 0xA0, 0x11, 0xF3, 0x8C, 0x0A, 0x4D, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1F, 0x9E, 0xDC, 0x7D, 0x04, 0xA5, 0x62, 0x72, 0x6B, 0xF5, 0x62, 0x75, 0x70, 0xF5, 0x62, 0x75, 0x76, 0xF5, 0x64, 0x70, - 0x6C, 0x61, 0x74, 0xF5, 0x69, 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x50, + 0x6C, 0x61, 0x74, 0xF4, 0x69, 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, 0xF4, 0x05, 0x19, 0x04, 0xB0, 0x06, 0x81, 0x01, }; -// AuthenticatorGetInfo request with all configurations equal to that of +// AuthenticatorGetInfo response with all configurations equal to that of +// kTestAuthenticatorGetInfoResponse except that U2F protocol is not supported. +constexpr uint8_t kTestCtap2OnlyAuthenticatorGetInfoResponse[] = { + 0x00, 0xA6, 0x01, 0x81, 0x68, 0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, + 0x5F, 0x30, 0x02, 0x82, 0x63, 0x75, 0x76, 0x6D, 0x6B, 0x68, 0x6D, + 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x03, 0x50, + 0xF8, 0xA0, 0x11, 0xF3, 0x8C, 0x0A, 0x4D, 0x15, 0x80, 0x06, 0x17, + 0x11, 0x1F, 0x9E, 0xDC, 0x7D, 0x04, 0xA5, 0x62, 0x72, 0x6B, 0xF5, + 0x62, 0x75, 0x70, 0xF5, 0x62, 0x75, 0x76, 0xF5, 0x64, 0x70, 0x6C, + 0x61, 0x74, 0xF4, 0x69, 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x50, + 0x69, 0x6E, 0xF4, 0x05, 0x19, 0x04, 0xB0, 0x06, 0x81, 0x01, +}; + +// AuthenticatorGetInfo response with all configurations equal to that of // kTestAuthenticatorGetInfoResponse except user verification option is set to // false. constexpr uint8_t kTestGetInfoResponseWithoutUvSupport[] = { @@ -1030,16 +1124,16 @@ constexpr uint8_t kTestGetInfoResponseWithoutResidentKeySupport[] = { // AuthenticatorGetInfo request with all configurations equal to that of // kTestAuthenticatorGetInfoResponse except platform device option is set to -// false. -constexpr uint8_t kTestGetInfoResponseCrossPlatformDevice[] = { +// true. +constexpr uint8_t kTestGetInfoResponsePlatformDevice[] = { 0x00, 0xA6, 0x01, 0x82, 0x68, 0x46, 0x49, 0x44, 0x4F, 0x5F, 0x32, 0x5F, 0x30, 0x66, 0x55, 0x32, 0x46, 0x5F, 0x56, 0x32, 0x02, 0x82, 0x63, 0x75, 0x76, 0x6D, 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x03, 0x50, 0xF8, 0xA0, 0x11, 0xF3, 0x8C, 0x0A, 0x4D, 0x15, 0x80, 0x06, 0x17, 0x11, 0x1F, 0x9E, 0xDC, 0x7D, 0x04, 0xA5, 0x62, 0x72, 0x6B, 0xF5, 0x62, 0x75, 0x70, 0xF5, 0x62, 0x75, 0x76, 0xF5, 0x64, 0x70, - // platform device : false - 0x6C, 0x61, 0x74, 0xF4, + // platform device : true + 0x6C, 0x61, 0x74, 0xF5, // End of platform_device setting. 0x69, 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, 0xF4, 0x05, 0x19, 0x04, 0xB0, 0x06, 0x81, 0x01, diff --git a/chromium/device/fido/fido_transport_protocol.cc b/chromium/device/fido/fido_transport_protocol.cc new file mode 100644 index 00000000000..2c98c529be7 --- /dev/null +++ b/chromium/device/fido/fido_transport_protocol.cc @@ -0,0 +1,57 @@ +// Copyright 2018 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 "device/fido/fido_transport_protocol.h" + +namespace device { + +const char kUsbHumanInterfaceDevice[] = "usb"; +const char kNearFieldCommunication[] = "nfc"; +const char kBluetoothLowEnergy[] = "ble"; +const char kCloudAssistedBluetoothLowEnergy[] = "cable"; +const char kInternal[] = "internal"; + +base::flat_set<FidoTransportProtocol> GetAllTransportProtocols() { + return {FidoTransportProtocol::kUsbHumanInterfaceDevice, + FidoTransportProtocol::kBluetoothLowEnergy, + FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy, + FidoTransportProtocol::kNearFieldCommunication, + FidoTransportProtocol::kInternal}; +} + +base::Optional<FidoTransportProtocol> ConvertToFidoTransportProtocol( + base::StringPiece protocol) { + if (protocol == kUsbHumanInterfaceDevice) + return FidoTransportProtocol::kUsbHumanInterfaceDevice; + else if (protocol == kNearFieldCommunication) + return FidoTransportProtocol::kNearFieldCommunication; + else if (protocol == kBluetoothLowEnergy) + return FidoTransportProtocol::kBluetoothLowEnergy; + else if (protocol == kCloudAssistedBluetoothLowEnergy) + return FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy; + else if (protocol == kInternal) + return FidoTransportProtocol::kInternal; + else + return base::nullopt; +} + +COMPONENT_EXPORT(DEVICE_FIDO) +std::string ToString(FidoTransportProtocol protocol) { + switch (protocol) { + case FidoTransportProtocol::kUsbHumanInterfaceDevice: + return kUsbHumanInterfaceDevice; + case FidoTransportProtocol::kNearFieldCommunication: + return kNearFieldCommunication; + case FidoTransportProtocol::kBluetoothLowEnergy: + return kBluetoothLowEnergy; + case FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy: + return kCloudAssistedBluetoothLowEnergy; + case FidoTransportProtocol::kInternal: + return kInternal; + } + NOTREACHED(); + return ""; +} + +} // namespace device diff --git a/chromium/device/fido/fido_transport_protocol.h b/chromium/device/fido/fido_transport_protocol.h index 71b09d95902..cf4847c8fbe 100644 --- a/chromium/device/fido/fido_transport_protocol.h +++ b/chromium/device/fido/fido_transport_protocol.h @@ -5,11 +5,18 @@ #ifndef DEVICE_FIDO_FIDO_TRANSPORT_PROTOCOL_H_ #define DEVICE_FIDO_FIDO_TRANSPORT_PROTOCOL_H_ +#include <string> + +#include "base/component_export.h" +#include "base/containers/flat_set.h" +#include "base/optional.h" +#include "base/strings/string_piece.h" + namespace device { // This enum represents the transport protocols over which Fido WebAuthN API is // currently supported. -enum class FidoTransportProtocol { +enum class FidoTransportProtocol : uint8_t { kUsbHumanInterfaceDevice, kNearFieldCommunication, kBluetoothLowEnergy, @@ -17,6 +24,23 @@ enum class FidoTransportProtocol { kInternal, }; +// String representation of above FidoTransportProtocol enum. +extern const char kUsbHumanInterfaceDevice[]; +extern const char kNearFieldCommunication[]; +extern const char kBluetoothLowEnergy[]; +extern const char kCloudAssistedBluetoothLowEnergy[]; +extern const char kInternal[]; + +COMPONENT_EXPORT(DEVICE_FIDO) +base::flat_set<FidoTransportProtocol> GetAllTransportProtocols(); + +COMPONENT_EXPORT(DEVICE_FIDO) +base::Optional<FidoTransportProtocol> ConvertToFidoTransportProtocol( + base::StringPiece protocol); + +COMPONENT_EXPORT(DEVICE_FIDO) +std::string ToString(FidoTransportProtocol protocol); + } // namespace device #endif // DEVICE_FIDO_FIDO_TRANSPORT_PROTOCOL_H_ diff --git a/chromium/device/fido/get_assertion_handler_unittest.cc b/chromium/device/fido/get_assertion_handler_unittest.cc index ed8267e85f3..e762657803b 100644 --- a/chromium/device/fido/get_assertion_handler_unittest.cc +++ b/chromium/device/fido/get_assertion_handler_unittest.cc @@ -5,11 +5,16 @@ #include <memory> #include <utility> +#include "base/bind.h" +#include "base/bind_helpers.h" #include "base/test/scoped_feature_list.h" #include "base/test/scoped_task_environment.h" #include "device/base/features.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" +#include "device/bluetooth/test/mock_bluetooth_adapter.h" #include "device/fido/authenticator_get_assertion_response.h" #include "device/fido/ctap_get_assertion_request.h" +#include "device/fido/device_response_converter.h" #include "device/fido/fake_fido_discovery.h" #include "device/fido/fido_constants.h" #include "device/fido/fido_parsing_utils.h" @@ -25,37 +30,108 @@ namespace device { namespace { -using TestGetAssertionRequestCallback = test::StatusAndValueCallbackReceiver< +constexpr uint8_t kBogusCredentialId[] = {0x01, 0x02, 0x03, 0x04}; + +using TestGetAssertionRequestCallback = test::StatusAndValuesCallbackReceiver< FidoReturnCode, - base::Optional<AuthenticatorGetAssertionResponse>>; + base::Optional<AuthenticatorGetAssertionResponse>, + FidoTransportProtocol>; } // namespace class FidoGetAssertionHandlerTest : public ::testing::Test { public: - void ForgeNextHidDiscovery() { + FidoGetAssertionHandlerTest() { + mock_adapter_ = + base::MakeRefCounted<::testing::NiceMock<MockBluetoothAdapter>>(); + BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter_); + } + + void ForgeDiscoveries() { discovery_ = scoped_fake_discovery_factory_.ForgeNextHidDiscovery(); + ble_discovery_ = scoped_fake_discovery_factory_.ForgeNextBleDiscovery(); + cable_discovery_ = scoped_fake_discovery_factory_.ForgeNextCableDiscovery(); + nfc_discovery_ = scoped_fake_discovery_factory_.ForgeNextNfcDiscovery(); + } + + CtapGetAssertionRequest CreateTestRequestWithCableExtension() { + CtapGetAssertionRequest request(test_data::kRelyingPartyId, + test_data::kClientDataHash); + request.SetCableExtension({}); + return request; } - std::unique_ptr<GetAssertionRequestHandler> CreateGetAssertionHandler() { + std::unique_ptr<GetAssertionRequestHandler> CreateGetAssertionHandlerU2f() { CtapGetAssertionRequest request(test_data::kRelyingPartyId, test_data::kClientDataHash); request.SetAllowList( {{CredentialType::kPublicKey, fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle)}}); + return CreateGetAssertionHandlerWithRequest(std::move(request)); + } + std::unique_ptr<GetAssertionRequestHandler> CreateGetAssertionHandlerCtap() { + CtapGetAssertionRequest request(test_data::kRelyingPartyId, + test_data::kClientDataHash); + request.SetAllowList({{CredentialType::kPublicKey, + fido_parsing_utils::Materialize( + test_data::kTestGetAssertionCredentialId)}}); return CreateGetAssertionHandlerWithRequest(std::move(request)); } std::unique_ptr<GetAssertionRequestHandler> CreateGetAssertionHandlerWithRequest(CtapGetAssertionRequest request) { - ForgeNextHidDiscovery(); + ForgeDiscoveries(); + + auto handler = std::make_unique<GetAssertionRequestHandler>( + nullptr /* connector */, supported_transports_, std::move(request), + get_assertion_cb_.callback()); + handler->SetPlatformAuthenticatorOrMarkUnavailable( + CreatePlatformAuthenticator()); + return handler; + } + + void ExpectAllowedTransportsForRequestAre( + GetAssertionRequestHandler* request_handler, + base::flat_set<FidoTransportProtocol> transports) { + using Transport = FidoTransportProtocol; + if (base::ContainsKey(transports, Transport::kUsbHumanInterfaceDevice)) + discovery()->WaitForCallToStartAndSimulateSuccess(); + if (base::ContainsKey(transports, Transport::kBluetoothLowEnergy)) + ble_discovery()->WaitForCallToStartAndSimulateSuccess(); + if (base::ContainsKey(transports, + Transport::kCloudAssistedBluetoothLowEnergy)) + cable_discovery()->WaitForCallToStartAndSimulateSuccess(); + if (base::ContainsKey(transports, Transport::kNearFieldCommunication)) + nfc_discovery()->WaitForCallToStartAndSimulateSuccess(); + + scoped_task_environment_.FastForwardUntilNoTasksRemain(); + EXPECT_FALSE(get_assertion_callback().was_called()); + + if (!base::ContainsKey(transports, Transport::kUsbHumanInterfaceDevice)) + EXPECT_FALSE(discovery()->is_start_requested()); + if (!base::ContainsKey(transports, Transport::kBluetoothLowEnergy)) + EXPECT_FALSE(ble_discovery()->is_start_requested()); + if (!base::ContainsKey(transports, + Transport::kCloudAssistedBluetoothLowEnergy)) + EXPECT_FALSE(cable_discovery()->is_start_requested()); + if (!base::ContainsKey(transports, Transport::kNearFieldCommunication)) + EXPECT_FALSE(nfc_discovery()->is_start_requested()); + + // Even with FidoTransportProtocol::kInternal allowed, unless the platform + // authenticator factory returns a FidoAuthenticator instance (which it will + // not be default), the transport will be marked `unavailable`. + transports.erase(Transport::kInternal); + + EXPECT_THAT( + request_handler->transport_availability_info().available_transports, + ::testing::UnorderedElementsAreArray(transports)); + } - return std::make_unique<GetAssertionRequestHandler>( - nullptr /* connector */, - base::flat_set<FidoTransportProtocol>( - {FidoTransportProtocol::kUsbHumanInterfaceDevice}), - std::move(request), get_assertion_cb_.callback()); + void ExpectAllTransportsAreAllowedForRequest( + GetAssertionRequestHandler* request_handler) { + ExpectAllowedTransportsForRequestAre(request_handler, + GetAllTransportProtocols()); } void InitFeatureListAndDisableCtapFlag() { @@ -63,28 +139,61 @@ class FidoGetAssertionHandlerTest : public ::testing::Test { } test::FakeFidoDiscovery* discovery() const { return discovery_; } + test::FakeFidoDiscovery* ble_discovery() const { return ble_discovery_; } + test::FakeFidoDiscovery* cable_discovery() const { return cable_discovery_; } + test::FakeFidoDiscovery* nfc_discovery() const { return nfc_discovery_; } TestGetAssertionRequestCallback& get_assertion_callback() { return get_assertion_cb_; } + void set_mock_platform_device(std::unique_ptr<MockFidoDevice> device) { + mock_platform_device_ = std::move(device); + } + + void set_supported_transports( + base::flat_set<FidoTransportProtocol> transports) { + supported_transports_ = std::move(transports); + } + protected: + base::Optional<PlatformAuthenticatorInfo> CreatePlatformAuthenticator() { + if (!mock_platform_device_) + return base::nullopt; + return PlatformAuthenticatorInfo( + std::make_unique<FidoDeviceAuthenticator>(mock_platform_device_.get()), + false /* has_recognized_mac_touch_id_credential_available */); + } + base::test::ScopedTaskEnvironment scoped_task_environment_{ base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME}; base::test::ScopedFeatureList scoped_feature_list_; test::ScopedFakeFidoDiscoveryFactory scoped_fake_discovery_factory_; test::FakeFidoDiscovery* discovery_; + test::FakeFidoDiscovery* ble_discovery_; + test::FakeFidoDiscovery* cable_discovery_; + test::FakeFidoDiscovery* nfc_discovery_; + scoped_refptr<::testing::NiceMock<MockBluetoothAdapter>> mock_adapter_; + std::unique_ptr<MockFidoDevice> mock_platform_device_; TestGetAssertionRequestCallback get_assertion_cb_; + base::flat_set<FidoTransportProtocol> supported_transports_ = + GetAllTransportProtocols(); }; -TEST_F(FidoGetAssertionHandlerTest, TestGetAssertionRequestOnSingleDevice) { - auto request_handler = CreateGetAssertionHandler(); - discovery()->WaitForCallToStartAndSimulateSuccess(); - auto device = std::make_unique<MockFidoDevice>(); +TEST_F(FidoGetAssertionHandlerTest, TransportAvailabilityInfo) { + auto request_handler = + CreateGetAssertionHandlerWithRequest(CtapGetAssertionRequest( + test_data::kRelyingPartyId, test_data::kClientDataHash)); - EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0")); - device->ExpectCtap2CommandAndRespondWith( - CtapRequestCommand::kAuthenticatorGetInfo, - test_data::kTestAuthenticatorGetInfoResponse); + EXPECT_EQ(FidoRequestHandlerBase::RequestType::kGetAssertion, + request_handler->transport_availability_info().request_type); + EXPECT_EQ(test_data::kRelyingPartyId, + request_handler->transport_availability_info().rp_id); +} + +TEST_F(FidoGetAssertionHandlerTest, CtapRequestOnSingleDevice) { + auto request_handler = CreateGetAssertionHandlerCtap(); + discovery()->WaitForCallToStartAndSimulateSuccess(); + auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(); device->ExpectCtap2CommandAndRespondWith( CtapRequestCommand::kAuthenticatorGetAssertion, test_data::kTestGetAssertionResponse); @@ -93,19 +202,16 @@ TEST_F(FidoGetAssertionHandlerTest, TestGetAssertionRequestOnSingleDevice) { get_assertion_callback().WaitForCallback(); EXPECT_EQ(FidoReturnCode::kSuccess, get_assertion_callback().status()); - EXPECT_TRUE(get_assertion_callback().value()); + EXPECT_TRUE(get_assertion_callback().value<0>()); EXPECT_TRUE(request_handler->is_complete()); } // Test a scenario where the connected authenticator is a U2F device. TEST_F(FidoGetAssertionHandlerTest, TestU2fSign) { - auto request_handler = CreateGetAssertionHandler(); + auto request_handler = CreateGetAssertionHandlerU2f(); discovery()->WaitForCallToStartAndSimulateSuccess(); - auto device = std::make_unique<MockFidoDevice>(); - EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0")); - device->ExpectCtap2CommandAndRespondWith( - CtapRequestCommand::kAuthenticatorGetInfo, base::nullopt); + auto device = MockFidoDevice::MakeU2fWithGetInfoExpectation(); device->ExpectRequestAndRespondWith( test_data::kU2fCheckOnlySignCommandApdu, test_data::kApduEncodedNoErrorSignResponse); @@ -116,7 +222,7 @@ TEST_F(FidoGetAssertionHandlerTest, TestU2fSign) { discovery()->AddDevice(std::move(device)); scoped_task_environment_.FastForwardUntilNoTasksRemain(); EXPECT_EQ(FidoReturnCode::kSuccess, get_assertion_callback().status()); - EXPECT_TRUE(get_assertion_callback().value()); + EXPECT_TRUE(get_assertion_callback().value<0>()); EXPECT_TRUE(request_handler->is_complete()); } @@ -124,7 +230,7 @@ TEST_F(FidoGetAssertionHandlerTest, TestU2fSign) { // "WebAuthenticationCtap2" flag is not enabled. TEST_F(FidoGetAssertionHandlerTest, TestU2fSignWithoutCtapFlag) { InitFeatureListAndDisableCtapFlag(); - auto request_handler = CreateGetAssertionHandler(); + auto request_handler = CreateGetAssertionHandlerU2f(); discovery()->WaitForCallToStartAndSimulateSuccess(); auto device = std::make_unique<MockFidoDevice>(); @@ -139,7 +245,7 @@ TEST_F(FidoGetAssertionHandlerTest, TestU2fSignWithoutCtapFlag) { discovery()->AddDevice(std::move(device)); scoped_task_environment_.FastForwardUntilNoTasksRemain(); EXPECT_EQ(FidoReturnCode::kSuccess, get_assertion_callback().status()); - EXPECT_TRUE(get_assertion_callback().value()); + EXPECT_TRUE(get_assertion_callback().value<0>()); EXPECT_TRUE(request_handler->is_complete()); } @@ -151,10 +257,7 @@ TEST_F(FidoGetAssertionHandlerTest, TestIncompatibleUserVerificationSetting) { CreateGetAssertionHandlerWithRequest(std::move(request)); discovery()->WaitForCallToStartAndSimulateSuccess(); - auto device = std::make_unique<MockFidoDevice>(); - EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0")); - device->ExpectCtap2CommandAndRespondWith( - CtapRequestCommand::kAuthenticatorGetInfo, + auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation( test_data::kTestGetInfoResponseWithoutUvSupport); discovery()->AddDevice(std::move(device)); @@ -175,10 +278,356 @@ TEST_F(FidoGetAssertionHandlerTest, CreateGetAssertionHandlerWithRequest(std::move(request)); discovery()->WaitForCallToStartAndSimulateSuccess(); - auto device = std::make_unique<MockFidoDevice>(); + auto device = MockFidoDevice::MakeU2fWithGetInfoExpectation(); + discovery()->AddDevice(std::move(device)); + + scoped_task_environment_.FastForwardUntilNoTasksRemain(); + EXPECT_FALSE(get_assertion_callback().was_called()); +} + +TEST_F(FidoGetAssertionHandlerTest, IncorrectRpIdHash) { + auto request_handler = + CreateGetAssertionHandlerWithRequest(CtapGetAssertionRequest( + test_data::kRelyingPartyId, test_data::kClientDataHash)); + discovery()->WaitForCallToStartAndSimulateSuccess(); + auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(); + device->ExpectCtap2CommandAndRespondWith( + CtapRequestCommand::kAuthenticatorGetAssertion, + test_data::kTestGetAssertionResponseWithIncorrectRpIdHash); + + discovery()->AddDevice(std::move(device)); + + scoped_task_environment_.FastForwardUntilNoTasksRemain(); + EXPECT_FALSE(get_assertion_callback().was_called()); +} + +// Tests a scenario where the authenticator responds with credential ID that +// is not included in the allowed list. +TEST_F(FidoGetAssertionHandlerTest, InvalidCredential) { + CtapGetAssertionRequest request(test_data::kRelyingPartyId, + test_data::kClientDataHash); + request.SetAllowList( + {{CredentialType::kPublicKey, + fido_parsing_utils::Materialize(test_data::kKeyHandleAlpha)}}); + auto request_handler = + CreateGetAssertionHandlerWithRequest(std::move(request)); + discovery()->WaitForCallToStartAndSimulateSuccess(); + // Resident Keys must be disabled, otherwise allow list check is skipped. + auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation( + test_data::kTestGetInfoResponseWithoutResidentKeySupport); + device->ExpectCtap2CommandAndRespondWith( + CtapRequestCommand::kAuthenticatorGetAssertion, + test_data::kTestGetAssertionResponse); + + discovery()->AddDevice(std::move(device)); + + scoped_task_environment_.FastForwardUntilNoTasksRemain(); + EXPECT_FALSE(get_assertion_callback().was_called()); +} + +// Tests a scenario where authenticator responds without user entity in its +// response but client is expecting a resident key credential. +TEST_F(FidoGetAssertionHandlerTest, IncorrectUserEntity) { + // Use a GetAssertion request with an empty allow list. + auto request_handler = + CreateGetAssertionHandlerWithRequest(CtapGetAssertionRequest( + test_data::kRelyingPartyId, test_data::kClientDataHash)); + discovery()->WaitForCallToStartAndSimulateSuccess(); + auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(); + device->ExpectCtap2CommandAndRespondWith( + CtapRequestCommand::kAuthenticatorGetAssertion, + test_data::kTestGetAssertionResponse); + + discovery()->AddDevice(std::move(device)); + + scoped_task_environment_.FastForwardUntilNoTasksRemain(); + EXPECT_FALSE(get_assertion_callback().was_called()); +} + +TEST_F(FidoGetAssertionHandlerTest, + AllTransportsAllowedIfAllowCredentialsListUndefined) { + auto request = CreateTestRequestWithCableExtension(); + EXPECT_CALL(*mock_adapter_, IsPresent()).WillOnce(::testing::Return(true)); + auto request_handler = + CreateGetAssertionHandlerWithRequest(std::move(request)); + ExpectAllTransportsAreAllowedForRequest(request_handler.get()); +} + +TEST_F(FidoGetAssertionHandlerTest, + AllTransportsAllowedIfAllowCredentialsListIsEmpty) { + auto request = CreateTestRequestWithCableExtension(); + request.SetAllowList({}); + EXPECT_CALL(*mock_adapter_, IsPresent()).WillOnce(::testing::Return(true)); + auto request_handler = + CreateGetAssertionHandlerWithRequest(std::move(request)); + ExpectAllTransportsAreAllowedForRequest(request_handler.get()); +} + +TEST_F(FidoGetAssertionHandlerTest, + AllTransportsAllowedIfHasAllowedCredentialWithEmptyTransportsList) { + auto request = CreateTestRequestWithCableExtension(); + request.SetAllowList({ + {CredentialType::kPublicKey, + fido_parsing_utils::Materialize( + test_data::kTestGetAssertionCredentialId), + {FidoTransportProtocol::kBluetoothLowEnergy}}, + {CredentialType::kPublicKey, + fido_parsing_utils::Materialize(kBogusCredentialId)}, + }); + + EXPECT_CALL(*mock_adapter_, IsPresent()).WillOnce(::testing::Return(true)); + auto request_handler = + CreateGetAssertionHandlerWithRequest(std::move(request)); + ExpectAllTransportsAreAllowedForRequest(request_handler.get()); +} + +TEST_F(FidoGetAssertionHandlerTest, + AllowedTransportsAreUnionOfTransportsLists) { + auto request = CreateTestRequestWithCableExtension(); + request.SetAllowList({ + {CredentialType::kPublicKey, + fido_parsing_utils::Materialize( + test_data::kTestGetAssertionCredentialId), + {FidoTransportProtocol::kBluetoothLowEnergy}}, + {CredentialType::kPublicKey, + fido_parsing_utils::Materialize(kBogusCredentialId), + {FidoTransportProtocol::kInternal, + FidoTransportProtocol::kNearFieldCommunication}}, + }); + + EXPECT_CALL(*mock_adapter_, IsPresent()).WillOnce(::testing::Return(true)); + auto request_handler = + CreateGetAssertionHandlerWithRequest(std::move(request)); + ExpectAllowedTransportsForRequestAre( + request_handler.get(), {FidoTransportProtocol::kBluetoothLowEnergy, + FidoTransportProtocol::kInternal, + FidoTransportProtocol::kNearFieldCommunication}); +} + +TEST_F(FidoGetAssertionHandlerTest, + CableDisabledIfAllowCredentialsListUndefinedButCableExtensionMissing) { + CtapGetAssertionRequest request(test_data::kRelyingPartyId, + test_data::kClientDataHash); + ASSERT_FALSE(!!request.cable_extension()); + EXPECT_CALL(*mock_adapter_, IsPresent()).WillOnce(::testing::Return(true)); + auto request_handler = + CreateGetAssertionHandlerWithRequest(std::move(request)); + ExpectAllowedTransportsForRequestAre( + request_handler.get(), {FidoTransportProtocol::kBluetoothLowEnergy, + FidoTransportProtocol::kUsbHumanInterfaceDevice, + FidoTransportProtocol::kNearFieldCommunication, + FidoTransportProtocol::kInternal}); +} + +TEST_F(FidoGetAssertionHandlerTest, + CableDisabledIfExplicitlyAllowedButCableExtensionMissing) { + CtapGetAssertionRequest request(test_data::kRelyingPartyId, + test_data::kClientDataHash); + ASSERT_FALSE(!!request.cable_extension()); + request.SetAllowList({ + {CredentialType::kPublicKey, + fido_parsing_utils::Materialize( + test_data::kTestGetAssertionCredentialId), + {FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy, + FidoTransportProtocol::kUsbHumanInterfaceDevice}}, + }); + + auto request_handler = + CreateGetAssertionHandlerWithRequest(std::move(request)); + ExpectAllowedTransportsForRequestAre( + request_handler.get(), {FidoTransportProtocol::kUsbHumanInterfaceDevice}); +} + +TEST_F(FidoGetAssertionHandlerTest, SupportedTransportsAreOnlyBleAndNfc) { + const base::flat_set<FidoTransportProtocol> kBleAndNfc = { + FidoTransportProtocol::kBluetoothLowEnergy, + FidoTransportProtocol::kNearFieldCommunication, + }; + + set_supported_transports(kBleAndNfc); + EXPECT_CALL(*mock_adapter_, IsPresent()).WillOnce(::testing::Return(true)); + auto request_handler = CreateGetAssertionHandlerWithRequest( + CreateTestRequestWithCableExtension()); + ExpectAllowedTransportsForRequestAre(request_handler.get(), kBleAndNfc); +} + +TEST_F(FidoGetAssertionHandlerTest, + SupportedTransportsAreOnlyCableAndInternal) { + const base::flat_set<FidoTransportProtocol> kCableAndInternal = { + FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy, + FidoTransportProtocol::kInternal, + }; + + EXPECT_CALL(*mock_adapter_, IsPresent()).WillOnce(::testing::Return(true)); + set_supported_transports(kCableAndInternal); + auto request_handler = CreateGetAssertionHandlerWithRequest( + CreateTestRequestWithCableExtension()); + ExpectAllowedTransportsForRequestAre(request_handler.get(), + kCableAndInternal); +} + +TEST_F(FidoGetAssertionHandlerTest, SuccessWithOnlyUsbTransportAllowed) { + auto request = CreateTestRequestWithCableExtension(); + request.SetAllowList({ + {CredentialType::kPublicKey, + fido_parsing_utils::Materialize( + test_data::kTestGetAssertionCredentialId), + {FidoTransportProtocol::kUsbHumanInterfaceDevice}}, + }); + + set_supported_transports({FidoTransportProtocol::kUsbHumanInterfaceDevice}); + + auto request_handler = + CreateGetAssertionHandlerWithRequest(std::move(request)); + + auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(); + device->ExpectCtap2CommandAndRespondWith( + CtapRequestCommand::kAuthenticatorGetAssertion, + test_data::kTestGetAssertionResponse); + discovery()->WaitForCallToStartAndSimulateSuccess(); + discovery()->AddDevice(std::move(device)); + + get_assertion_callback().WaitForCallback(); + + EXPECT_EQ(FidoReturnCode::kSuccess, get_assertion_callback().status()); + EXPECT_TRUE(get_assertion_callback().value<0>()); + EXPECT_TRUE(request_handler->is_complete()); + EXPECT_THAT( + request_handler->transport_availability_info().available_transports, + ::testing::UnorderedElementsAre( + FidoTransportProtocol::kUsbHumanInterfaceDevice)); +} + +TEST_F(FidoGetAssertionHandlerTest, SuccessWithOnlyBleTransportAllowed) { + auto request = CreateTestRequestWithCableExtension(); + request.SetAllowList({ + {CredentialType::kPublicKey, + fido_parsing_utils::Materialize( + test_data::kTestGetAssertionCredentialId), + {FidoTransportProtocol::kBluetoothLowEnergy}}, + }); + + set_supported_transports({FidoTransportProtocol::kBluetoothLowEnergy}); + EXPECT_CALL(*mock_adapter_, IsPresent()).WillOnce(::testing::Return(true)); + auto request_handler = + CreateGetAssertionHandlerWithRequest(std::move(request)); + + auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(); + device->SetDeviceTransport(FidoTransportProtocol::kBluetoothLowEnergy); + device->ExpectCtap2CommandAndRespondWith( + CtapRequestCommand::kAuthenticatorGetAssertion, + test_data::kTestGetAssertionResponse); + ble_discovery()->WaitForCallToStartAndSimulateSuccess(); + ble_discovery()->AddDevice(std::move(device)); + + get_assertion_callback().WaitForCallback(); + + EXPECT_EQ(FidoReturnCode::kSuccess, get_assertion_callback().status()); + EXPECT_TRUE(get_assertion_callback().value<0>()); + EXPECT_TRUE(request_handler->is_complete()); + EXPECT_THAT( + request_handler->transport_availability_info().available_transports, + ::testing::UnorderedElementsAre( + FidoTransportProtocol::kBluetoothLowEnergy)); +} + +TEST_F(FidoGetAssertionHandlerTest, SuccessWithOnlyNfcTransportAllowed) { + auto request = CreateTestRequestWithCableExtension(); + request.SetAllowList({ + {CredentialType::kPublicKey, + fido_parsing_utils::Materialize( + test_data::kTestGetAssertionCredentialId), + {FidoTransportProtocol::kNearFieldCommunication}}, + }); + + set_supported_transports({FidoTransportProtocol::kNearFieldCommunication}); + + auto request_handler = + CreateGetAssertionHandlerWithRequest(std::move(request)); + + auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(); + device->SetDeviceTransport(FidoTransportProtocol::kNearFieldCommunication); + device->ExpectCtap2CommandAndRespondWith( + CtapRequestCommand::kAuthenticatorGetAssertion, + test_data::kTestGetAssertionResponse); + nfc_discovery()->WaitForCallToStartAndSimulateSuccess(); + nfc_discovery()->AddDevice(std::move(device)); + + get_assertion_callback().WaitForCallback(); + + EXPECT_EQ(FidoReturnCode::kSuccess, get_assertion_callback().status()); + EXPECT_TRUE(get_assertion_callback().value<0>()); + EXPECT_TRUE(request_handler->is_complete()); + EXPECT_THAT( + request_handler->transport_availability_info().available_transports, + ::testing::UnorderedElementsAre( + FidoTransportProtocol::kNearFieldCommunication)); +} + +TEST_F(FidoGetAssertionHandlerTest, SuccessWithOnlyInternalTransportAllowed) { + auto request = CreateTestRequestWithCableExtension(); + request.SetAllowList({ + {CredentialType::kPublicKey, + fido_parsing_utils::Materialize( + test_data::kTestGetAssertionCredentialId), + {FidoTransportProtocol::kInternal}}, + }); + + set_supported_transports({FidoTransportProtocol::kInternal}); + + auto device = MockFidoDevice::MakeCtap( + ReadCTAPGetInfoResponse(test_data::kTestGetInfoResponsePlatformDevice)); EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0")); + device->SetDeviceTransport(FidoTransportProtocol::kInternal); + device->ExpectCtap2CommandAndRespondWith( + CtapRequestCommand::kAuthenticatorGetInfo, + test_data::kTestGetInfoResponsePlatformDevice); + device->ExpectCtap2CommandAndRespondWith( + CtapRequestCommand::kAuthenticatorGetAssertion, + test_data::kTestGetAssertionResponse); + set_mock_platform_device(std::move(device)); + + auto request_handler = + CreateGetAssertionHandlerWithRequest(std::move(request)); + get_assertion_callback().WaitForCallback(); + + EXPECT_EQ(FidoReturnCode::kSuccess, get_assertion_callback().status()); + EXPECT_TRUE(get_assertion_callback().value<0>()); + EXPECT_TRUE(request_handler->is_complete()); + EXPECT_THAT( + request_handler->transport_availability_info().available_transports, + ::testing::UnorderedElementsAre(FidoTransportProtocol::kInternal)); +} + +// Tests a scenario where authenticator of incorrect transport type was used to +// conduct CTAP GetAssertion call. +// +// TODO(engedy): This should not happen, instead |allowCredentials| should be +// filtered to only contain items compatible with the transport actually used to +// talk to the authenticator. +TEST_F(FidoGetAssertionHandlerTest, IncorrectTransportType) { + // GetAssertion request that expects GetAssertion call for credential + // |CredentialType::kPublicKey| to be signed with Cable authenticator. + auto request = CreateTestRequestWithCableExtension(); + request.SetAllowList({ + {CredentialType::kPublicKey, + fido_parsing_utils::Materialize( + test_data::kTestGetAssertionCredentialId), + {FidoTransportProtocol::kBluetoothLowEnergy}}, + {CredentialType::kPublicKey, + fido_parsing_utils::Materialize(kBogusCredentialId), + {FidoTransportProtocol::kUsbHumanInterfaceDevice}}, + }); + auto request_handler = + CreateGetAssertionHandlerWithRequest(std::move(request)); + discovery()->WaitForCallToStartAndSimulateSuccess(); + auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(); + // Since transport type of |device| is different from what the relying party + // defined in |request| above, this request should fail. + device->SetDeviceTransport(FidoTransportProtocol::kUsbHumanInterfaceDevice); device->ExpectCtap2CommandAndRespondWith( - CtapRequestCommand::kAuthenticatorGetInfo, base::nullopt); + CtapRequestCommand::kAuthenticatorGetAssertion, + test_data::kTestGetAssertionResponse); discovery()->AddDevice(std::move(device)); @@ -186,4 +635,53 @@ TEST_F(FidoGetAssertionHandlerTest, EXPECT_FALSE(get_assertion_callback().was_called()); } +// If a device with transport type kInternal returns a +// CTAP2_ERR_OPERATION_DENIED error, the request should complete with +// FidoReturnCode::kUserConsentDenied. Pending authenticators should be +// cancelled. +TEST_F(FidoGetAssertionHandlerTest, + TestRequestWithOperationDeniedErrorPlatform) { + auto platform_device = MockFidoDevice::MakeCtapWithGetInfoExpectation( + test_data::kTestGetInfoResponsePlatformDevice); + platform_device->SetDeviceTransport(FidoTransportProtocol::kInternal); + platform_device->ExpectCtap2CommandAndRespondWithError( + CtapRequestCommand::kAuthenticatorGetAssertion, + CtapDeviceResponseCode::kCtap2ErrOperationDenied, + base::TimeDelta::FromMicroseconds(10)); + set_mock_platform_device(std::move(platform_device)); + + auto other_device = MockFidoDevice::MakeCtapWithGetInfoExpectation(); + other_device->ExpectCtap2CommandAndDoNotRespond( + CtapRequestCommand::kAuthenticatorGetAssertion); + EXPECT_CALL(*other_device, Cancel); + + auto request_handler = CreateGetAssertionHandlerCtap(); + discovery()->WaitForCallToStartAndSimulateSuccess(); + discovery()->AddDevice(std::move(other_device)); + + scoped_task_environment_.FastForwardUntilNoTasksRemain(); + EXPECT_TRUE(get_assertion_callback().was_called()); + EXPECT_EQ(FidoReturnCode::kUserConsentDenied, + get_assertion_callback().status()); +} + +// Like |TestRequestWithOperationDeniedErrorPlatform|, but with a +// cross-platform device. +TEST_F(FidoGetAssertionHandlerTest, + TestRequestWithOperationDeniedErrorCrossPlatform) { + auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(); + device->ExpectCtap2CommandAndRespondWithError( + CtapRequestCommand::kAuthenticatorGetAssertion, + CtapDeviceResponseCode::kCtap2ErrOperationDenied); + + auto request_handler = CreateGetAssertionHandlerCtap(); + discovery()->WaitForCallToStartAndSimulateSuccess(); + discovery()->AddDevice(std::move(device)); + + scoped_task_environment_.FastForwardUntilNoTasksRemain(); + EXPECT_TRUE(get_assertion_callback().was_called()); + EXPECT_EQ(FidoReturnCode::kUserConsentDenied, + get_assertion_callback().status()); +} + } // namespace device diff --git a/chromium/device/fido/get_assertion_request_handler.cc b/chromium/device/fido/get_assertion_request_handler.cc index dcf5aa04b6b..1e7888a8068 100644 --- a/chromium/device/fido/get_assertion_request_handler.cc +++ b/chromium/device/fido/get_assertion_request_handler.cc @@ -4,54 +4,85 @@ #include "device/fido/get_assertion_request_handler.h" +#include <algorithm> +#include <set> #include <utility> #include "base/bind.h" +#include "base/stl_util.h" #include "device/fido/authenticator_get_assertion_response.h" +#include "device/fido/cable/fido_cable_discovery.h" #include "device/fido/fido_authenticator.h" -#include "device/fido/fido_cable_discovery.h" #include "device/fido/get_assertion_task.h" namespace device { -GetAssertionRequestHandler::GetAssertionRequestHandler( - service_manager::Connector* connector, - const base::flat_set<FidoTransportProtocol>& protocols, - CtapGetAssertionRequest request, - SignResponseCallback completion_callback) - : GetAssertionRequestHandler(connector, - protocols, - std::move(request), - std::move(completion_callback), - AddPlatformAuthenticatorCallback()) {} +namespace { -GetAssertionRequestHandler::GetAssertionRequestHandler( - service_manager::Connector* connector, - const base::flat_set<FidoTransportProtocol>& protocols, - CtapGetAssertionRequest request, - SignResponseCallback completion_callback, - AddPlatformAuthenticatorCallback add_platform_authenticator) - : FidoRequestHandler(connector, - protocols, - std::move(completion_callback), - std::move(add_platform_authenticator)), - request_(std::move(request)), - weak_factory_(this) { - if (base::ContainsKey( - protocols, FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy) && - request_.cable_extension()) { - auto discovery = - std::make_unique<FidoCableDiscovery>(*request_.cable_extension()); - discovery->set_observer(this); - discoveries().push_back(std::move(discovery)); +// PublicKeyUserEntity field in GetAssertion response is optional with the +// following constraints: +// - If assertion has been made without user verification, user identifiable +// information must not be included. +// - For resident key credentials, user id of the user entity is mandatory. +// - When multiple accounts exist for specified RP ID, user entity is +// mandatory. +// TODO(hongjunchoi) : Add link to section of the CTAP spec once it is +// published. +bool CheckRequirementsOnResponseUserEntity( + const CtapGetAssertionRequest& request, + const AuthenticatorGetAssertionResponse& response) { + // If assertion has been made without user verification, user identifiable + // information must not be included. + const auto& user_entity = response.user_entity(); + const bool has_user_identifying_info = + user_entity && (user_entity->user_display_name() || + user_entity->user_name() || user_entity->user_icon_url()); + if (!response.auth_data().obtained_user_verification() && + has_user_identifying_info) { + return false; } - Start(); -} + // For resident key credentials, user id of the user entity is mandatory. + if ((!request.allow_list() || request.allow_list()->empty()) && + !user_entity) { + return false; + } -GetAssertionRequestHandler::~GetAssertionRequestHandler() = default; + // When multiple accounts exist for specified RP ID, user entity is mandatory. + if (response.num_credentials().value_or(0u) > 1 && !user_entity) { + return false; + } -namespace { + return true; +} + +// Checks whether credential ID returned from the authenticator and transport +// type used matches the transport type and credential ID defined in +// PublicKeyCredentialDescriptor of the allowed list. If the device has resident +// key support, returned credential ID may be resident credential. Thus, +// returned credential ID need not be in allowed list. +// TODO(hongjunchoi) : Add link to section of the CTAP spec once it is +// published. +bool CheckResponseCredentialIdMatchesRequestAllowList( + const FidoAuthenticator& authenticator, + const CtapGetAssertionRequest request, + const AuthenticatorGetAssertionResponse& response) { + const auto& allow_list = request.allow_list(); + if (!allow_list || allow_list->empty()) { + // Allow list can't be empty for authenticators w/o resident key support. + return authenticator.Options().supports_resident_key(); + } + // Credential ID may be omitted if allow list has size 1. Otherwise, it needs + // to match. + const auto transport_used = authenticator.AuthenticatorTransport(); + return (allow_list->size() == 1 && !response.credential()) || + std::any_of(allow_list->cbegin(), allow_list->cend(), + [&response, transport_used](const auto& credential) { + return credential.id() == response.raw_credential_id() && + base::ContainsKey(credential.transports(), + transport_used); + }); +} // Checks UserVerificationRequirement enum passed from the relying party is // compatible with the authenticator, and updates the request to the @@ -86,8 +117,74 @@ bool CheckUserVerificationCompatible(FidoAuthenticator* authenticator, return false; } +base::flat_set<FidoTransportProtocol> GetTransportsAllowedByRP( + const CtapGetAssertionRequest& request) { + const base::flat_set<FidoTransportProtocol> kAllTransports = { + FidoTransportProtocol::kInternal, + FidoTransportProtocol::kNearFieldCommunication, + FidoTransportProtocol::kUsbHumanInterfaceDevice, + FidoTransportProtocol::kBluetoothLowEnergy, + FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy}; + + // TODO(https://crbug.com/874479): |allowed_list| will |has_value| even if the + // WebAuthn request has `allowCredential` undefined. + const auto& allowed_list = request.allow_list(); + if (!allowed_list || allowed_list->empty()) { + return kAllTransports; + } + + base::flat_set<FidoTransportProtocol> transports; + for (const auto credential : *allowed_list) { + if (credential.transports().empty()) + return kAllTransports; + transports.insert(credential.transports().begin(), + credential.transports().end()); + } + + return transports; +} + +base::flat_set<FidoTransportProtocol> GetTransportsAllowedAndConfiguredByRP( + const CtapGetAssertionRequest& request) { + auto transports = GetTransportsAllowedByRP(request); + if (!request.cable_extension()) + transports.erase(FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy); + return transports; +} + } // namespace +GetAssertionRequestHandler::GetAssertionRequestHandler( + service_manager::Connector* connector, + const base::flat_set<FidoTransportProtocol>& supported_transports, + CtapGetAssertionRequest request, + SignResponseCallback completion_callback) + : FidoRequestHandler( + connector, + base::STLSetIntersection<base::flat_set<FidoTransportProtocol>>( + supported_transports, + GetTransportsAllowedAndConfiguredByRP(request)), + std::move(completion_callback)), + request_(std::move(request)), + weak_factory_(this) { + transport_availability_info().rp_id = request_.rp_id(); + transport_availability_info().request_type = + FidoRequestHandlerBase::RequestType::kGetAssertion; + + if (base::ContainsKey( + transport_availability_info().available_transports, + FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy)) { + DCHECK(request_.cable_extension()); + auto discovery = FidoDiscovery::CreateCable(*request_.cable_extension()); + discovery->set_observer(this); + discoveries().push_back(std::move(discovery)); + } + + Start(); +} + +GetAssertionRequestHandler::~GetAssertionRequestHandler() = default; + void GetAssertionRequestHandler::DispatchRequest( FidoAuthenticator* authenticator) { // The user verification field of the request may be adjusted to the @@ -99,8 +196,29 @@ void GetAssertionRequestHandler::DispatchRequest( authenticator->GetAssertion( std::move(request_copy), - base::BindOnce(&GetAssertionRequestHandler::OnAuthenticatorResponse, + base::BindOnce(&GetAssertionRequestHandler::HandleResponse, weak_factory_.GetWeakPtr(), authenticator)); } +void GetAssertionRequestHandler::HandleResponse( + FidoAuthenticator* authenticator, + CtapDeviceResponseCode response_code, + base::Optional<AuthenticatorGetAssertionResponse> response) { + if (response_code != CtapDeviceResponseCode::kSuccess) { + OnAuthenticatorResponse(authenticator, response_code, base::nullopt); + return; + } + + if (!response || !request_.CheckResponseRpIdHash(response->GetRpIdHash()) || + !CheckResponseCredentialIdMatchesRequestAllowList(*authenticator, + request_, *response) || + !CheckRequirementsOnResponseUserEntity(request_, *response)) { + OnAuthenticatorResponse( + authenticator, CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt); + return; + } + + OnAuthenticatorResponse(authenticator, response_code, std::move(response)); +} + } // namespace device diff --git a/chromium/device/fido/get_assertion_request_handler.h b/chromium/device/fido/get_assertion_request_handler.h index 594a89c1657..de6e0d378f9 100644 --- a/chromium/device/fido/get_assertion_request_handler.h +++ b/chromium/device/fido/get_assertion_request_handler.h @@ -27,28 +27,28 @@ class AuthenticatorGetAssertionResponse; using SignResponseCallback = base::OnceCallback<void(FidoReturnCode, - base::Optional<AuthenticatorGetAssertionResponse>)>; + base::Optional<AuthenticatorGetAssertionResponse>, + FidoTransportProtocol)>; class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionRequestHandler : public FidoRequestHandler<AuthenticatorGetAssertionResponse> { public: GetAssertionRequestHandler( service_manager::Connector* connector, - const base::flat_set<FidoTransportProtocol>& protocols, + const base::flat_set<FidoTransportProtocol>& supported_transports, CtapGetAssertionRequest request_parameter, SignResponseCallback completion_callback); - GetAssertionRequestHandler( - service_manager::Connector* connector, - const base::flat_set<FidoTransportProtocol>& protocols, - CtapGetAssertionRequest request_parameter, - SignResponseCallback completion_callback, - AddPlatformAuthenticatorCallback add_platform_authenticator); ~GetAssertionRequestHandler() override; private: // FidoRequestHandlerBase: void DispatchRequest(FidoAuthenticator* authenticator) override; + void HandleResponse( + FidoAuthenticator* authenticator, + CtapDeviceResponseCode response_code, + base::Optional<AuthenticatorGetAssertionResponse> response); + CtapGetAssertionRequest request_; base::WeakPtrFactory<GetAssertionRequestHandler> weak_factory_; diff --git a/chromium/device/fido/get_assertion_task.cc b/chromium/device/fido/get_assertion_task.cc index b9ae015deb0..8fb08533bcb 100644 --- a/chromium/device/fido/get_assertion_task.cc +++ b/chromium/device/fido/get_assertion_task.cc @@ -4,30 +4,27 @@ #include "device/fido/get_assertion_task.h" -#include <algorithm> #include <utility> #include "base/bind.h" #include "device/base/features.h" #include "device/fido/authenticator_get_assertion_response.h" #include "device/fido/ctap2_device_operation.h" -#include "device/fido/ctap_empty_authenticator_request.h" -#include "device/fido/device_response_converter.h" -#include "device/fido/u2f_command_constructor.h" #include "device/fido/u2f_sign_operation.h" namespace device { namespace { -bool ResponseContainsUserIdentifiableInfo( - const AuthenticatorGetAssertionResponse& response) { - const auto& user_entity = response.user_entity(); - if (!user_entity) - return false; - - return user_entity->user_display_name() || user_entity->user_name() || - user_entity->user_icon_url(); +bool MayFallbackToU2fWithAppIdExtension( + const FidoDevice& device, + const CtapGetAssertionRequest& request) { + bool ctap2_device_supports_u2f = + device.device_info() && + base::ContainsKey(device.device_info()->versions(), + ProtocolVersion::kU2f); + return request.alternative_application_parameter() && + ctap2_device_supports_u2f; } } // namespace @@ -51,87 +48,76 @@ void GetAssertionTask::StartTask() { } } -void GetAssertionTask::GetAssertion() { +void GetAssertionTask::GetAssertion(bool enforce_user_presence) { + // If appId extension was used in the request and device is a hybrid U2F/CTAP2 + // device, then first issue a silent GetAssertionRequest. If no credentials in + // allowed credential list are recognized, it's possible that the credential + // is registered via U2F. Under these circumstances, the request should be + // issued via the U2F protocol. Otherwise, proceed with a normal GetAssertion + // request. + auto uv_configuration = request_.user_verification(); + bool is_silent_authentication = false; + if (!enforce_user_presence && + MayFallbackToU2fWithAppIdExtension(*device(), request_)) { + is_silent_authentication = true; + request_.SetUserPresenceRequired(false /* user_presence_required */); + request_.SetUserVerification(UserVerificationRequirement::kDiscouraged); + } + sign_operation_ = std::make_unique<Ctap2DeviceOperation<CtapGetAssertionRequest, AuthenticatorGetAssertionResponse>>( device(), request_, - base::BindOnce(&GetAssertionTask::OnCtapGetAssertionResponseReceived, - weak_factory_.GetWeakPtr()), + base::BindOnce(&GetAssertionTask::GetAssertionCallbackWithU2fFallback, + weak_factory_.GetWeakPtr(), is_silent_authentication, + uv_configuration, std::move(callback_)), base::BindOnce(&ReadCTAPGetAssertionResponse)); sign_operation_->Start(); } void GetAssertionTask::U2fSign() { - DCHECK(!device()->device_info()); DCHECK_EQ(ProtocolVersion::kU2f, device()->supported_protocol()); - sign_operation_ = std::make_unique<U2fSignOperation>( - device(), request_, - base::BindOnce(&GetAssertionTask::OnCtapGetAssertionResponseReceived, - weak_factory_.GetWeakPtr())); + sign_operation_ = std::make_unique<U2fSignOperation>(device(), request_, + std::move(callback_)); sign_operation_->Start(); } -bool GetAssertionTask::CheckRequirementsOnReturnedUserEntities( - const AuthenticatorGetAssertionResponse& response) { - // If assertion has been made without user verification, user identifiable - // information must not be included. - if (!response.auth_data().obtained_user_verification() && - ResponseContainsUserIdentifiableInfo(response)) { - return false; - } - - // For resident key credentials, user id of the user entity is mandatory. - if ((!request_.allow_list() || request_.allow_list()->empty()) && - !response.user_entity()) { - return false; - } - - // When multiple accounts exist for specified RP ID, user entity is mandatory. - if (response.num_credentials().value_or(0u) > 1 && !response.user_entity()) { - return false; - } - - return true; -} - -bool GetAssertionTask::CheckRequirementsOnReturnedCredentialId( - const AuthenticatorGetAssertionResponse& response) { - if (device()->device_info() && - device()->device_info()->options().supports_resident_key()) { - return true; - } - - const auto& allow_list = request_.allow_list(); - return allow_list && - (allow_list->size() == 1 || - std::any_of(allow_list->cbegin(), allow_list->cend(), - [&response](const auto& credential) { - return credential.id() == response.raw_credential_id(); - })); -} - -void GetAssertionTask::OnCtapGetAssertionResponseReceived( +void GetAssertionTask::GetAssertionCallbackWithU2fFallback( + bool is_silent_authentication, + UserVerificationRequirement user_verification_configuration, + GetAssertionTaskCallback callback, CtapDeviceResponseCode response_code, - base::Optional<AuthenticatorGetAssertionResponse> device_response) { - if (response_code != CtapDeviceResponseCode::kSuccess) { - std::move(callback_).Run(response_code, base::nullopt); + base::Optional<AuthenticatorGetAssertionResponse> response_data) { + DCHECK(device()->device_info()); + if (!(is_silent_authentication && + MayFallbackToU2fWithAppIdExtension(*device(), request_))) { + std::move(callback).Run(response_code, std::move(response_data)); return; } - // TODO(martinkr): CheckRpIdHash invocation needs to move into the Request - // handler. See https://crbug.com/863988. - if (!device_response || - !request_.CheckResponseRpIdHash(device_response->GetRpIdHash()) || - !CheckRequirementsOnReturnedCredentialId(*device_response) || - !CheckRequirementsOnReturnedUserEntities(*device_response)) { - std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther, - base::nullopt); - return; + DCHECK(!callback_ && !request_.user_presence_required()); + request_.SetUserPresenceRequired(true /* true */); + callback_ = std::move(callback); + + // Credential was recognized by the device. As this authentication was + // silent authentication (i.e. user touch was not provided), try again with + // user presence enforced and with the original user verification + // configuration. + if (response_code == CtapDeviceResponseCode::kSuccess) { + DCHECK_EQ(UserVerificationRequirement::kDiscouraged, + request_.user_verification()); + DCHECK_EQ(ProtocolVersion::kCtap, device()->supported_protocol()); + request_.SetUserVerification(user_verification_configuration); + GetAssertion(true /* enforce_user_presence */); + } else { + // An error occurred or no credentials in the allowed list were recognized. + // However, as the relying party has provided appId extension, try again + // with U2F protocol to make sure that authentication via appID credential + // is also attempted. + device()->set_supported_protocol(ProtocolVersion::kU2f); + U2fSign(); } - - std::move(callback_).Run(response_code, std::move(device_response)); } } // namespace device diff --git a/chromium/device/fido/get_assertion_task.h b/chromium/device/fido/get_assertion_task.h index b993d84c3f6..a5900235b48 100644 --- a/chromium/device/fido/get_assertion_task.h +++ b/chromium/device/fido/get_assertion_task.h @@ -43,33 +43,19 @@ class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionTask : public FidoTask { // FidoTask: void StartTask() override; - void GetAssertion(); + void GetAssertion(bool enforce_user_presence = false); void U2fSign(); - // PublicKeyUserEntity field in GetAssertion response is optional with the - // following constraints: - // - If assertion has been made without user verification, user identifiable - // information must not be included. - // - For resident key credentials, user id of the user entity is mandatory. - // - When multiple accounts exist for specified RP ID, user entity is - // mandatory. - // TODO(hongjunchoi) : Add link to section of the CTAP spec once it is - // published. - bool CheckRequirementsOnReturnedUserEntities( - const AuthenticatorGetAssertionResponse& response); - - // Checks whether credential ID returned from the authenticator was included - // in the allowed list for authenticators. If the device has resident key - // support, returned credential ID may be resident credential. Thus, returned - // credential ID need not be in allowed list. - // TODO(hongjunchoi) : Add link to section of the CTAP spec once it is - // published. - bool CheckRequirementsOnReturnedCredentialId( - const AuthenticatorGetAssertionResponse& response); - - void OnCtapGetAssertionResponseReceived( - CtapDeviceResponseCode response_code, - base::Optional<AuthenticatorGetAssertionResponse> device_response); + // Callback logic for CTAP2 GetAssertion. This will fall back to U2F on hybrid + // U2F/CTAP2 devices when: + // a) No credentials were recognized via CTAP2 and, + // b) The request contains the appID extension. + void GetAssertionCallbackWithU2fFallback( + bool is_silent_authentication, + UserVerificationRequirement user_verification_required, + GetAssertionTaskCallback callback, + CtapDeviceResponseCode, + base::Optional<AuthenticatorGetAssertionResponse>); CtapGetAssertionRequest request_; std::unique_ptr<SignOperation> sign_operation_; diff --git a/chromium/device/fido/get_assertion_task_unittest.cc b/chromium/device/fido/get_assertion_task_unittest.cc index db9183c8e7b..1ed026af0d3 100644 --- a/chromium/device/fido/get_assertion_task_unittest.cc +++ b/chromium/device/fido/get_assertion_task_unittest.cc @@ -177,13 +177,10 @@ TEST_F(FidoGetAssertionTaskTest, TestU2fSignWithoutFlag) { EXPECT_TRUE(get_assertion_callback_receiver().value()); } -// Tests a scenario where the authenticator responds with credential ID that -// is not included in the allowed list. -TEST_F(FidoGetAssertionTaskTest, TestGetAssertionInvalidCredential) { +TEST_F(FidoGetAssertionTaskTest, TestIncorrectGetAssertionResponse) { auto device = MockFidoDevice::MakeCtap(); device->ExpectCtap2CommandAndRespondWith( - CtapRequestCommand::kAuthenticatorGetAssertion, - test_data::kTestGetAssertionResponse); + CtapRequestCommand::kAuthenticatorGetAssertion, base::nullopt); auto task = std::make_unique<GetAssertionTask>( device.get(), @@ -197,78 +194,123 @@ TEST_F(FidoGetAssertionTaskTest, TestGetAssertionInvalidCredential) { EXPECT_FALSE(get_assertion_callback_receiver().value()); } -// Tests a scenario where authenticator responds without user entity in its -// response but client is expecting a resident key credential. -TEST_F(FidoGetAssertionTaskTest, TestGetAsserionIncorrectUserEntity) { - auto device = MockFidoDevice::MakeCtap(); - device->ExpectCtap2CommandAndRespondWith( - CtapRequestCommand::kAuthenticatorGetAssertion, - test_data::kTestGetAssertionResponse); +TEST_F(FidoGetAssertionTaskTest, TestU2fSignRequestWithEmptyAllowedList) { + auto request = CtapGetAssertionRequest(test_data::kRelyingPartyId, + test_data::kClientDataHash); + + auto device = MockFidoDevice::MakeU2f(); + device->ExpectRequestAndRespondWith( + test_data::kU2fFakeRegisterCommand, + test_data::kApduEncodedNoErrorSignResponse); auto task = std::make_unique<GetAssertionTask>( - device.get(), - CtapGetAssertionRequest(test_data::kRelyingPartyId, - test_data::kClientDataHash), + device.get(), std::move(request), get_assertion_callback_receiver().callback()); get_assertion_callback_receiver().WaitForCallback(); - EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrOther, + EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrNoCredentials, get_assertion_callback_receiver().status()); EXPECT_FALSE(get_assertion_callback_receiver().value()); } -TEST_F(FidoGetAssertionTaskTest, TestGetAsserionIncorrectRpIdHash) { +// Checks that when device supports both CTAP2 and U2F protocol and when +// appId extension parameter is present, the browser first checks presence +// of valid credentials via silent authentication. +TEST_F(FidoGetAssertionTaskTest, TestSilentSignInWhenAppIdExtensionPresent) { + CtapGetAssertionRequest request(test_data::kRelyingPartyId, + test_data::kClientDataHash); + + std::vector<PublicKeyCredentialDescriptor> allowed_list; + allowed_list.push_back(PublicKeyCredentialDescriptor( + CredentialType::kPublicKey, + fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle))); + request.SetAlternativeApplicationParameter( + test_data::kAlternativeApplicationParameter); + request.SetAllowList(std::move(allowed_list)); + auto device = MockFidoDevice::MakeCtap(); - device->ExpectCtap2CommandAndRespondWith( - CtapRequestCommand::kAuthenticatorGetAssertion, - test_data::kTestGetAssertionResponseWithIncorrectRpIdHash); + device->ExpectRequestAndRespondWith(test_data::kCtapSilentGetAssertionRequest, + test_data::kTestGetAssertionResponse); + device->ExpectRequestAndRespondWith(test_data::kCtapGetAssertionRequest, + test_data::kTestGetAssertionResponse); auto task = std::make_unique<GetAssertionTask>( - device.get(), - CtapGetAssertionRequest(test_data::kRelyingPartyId, - test_data::kClientDataHash), + device.get(), std::move(request), get_assertion_callback_receiver().callback()); get_assertion_callback_receiver().WaitForCallback(); - EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrOther, + EXPECT_EQ(CtapDeviceResponseCode::kSuccess, get_assertion_callback_receiver().status()); - EXPECT_FALSE(get_assertion_callback_receiver().value()); } -TEST_F(FidoGetAssertionTaskTest, TestIncorrectGetAssertionResponse) { +TEST_F(FidoGetAssertionTaskTest, TestU2fFallbackForAppIdExtension) { + CtapGetAssertionRequest request(test_data::kRelyingPartyId, + test_data::kClientDataHash); + + std::vector<PublicKeyCredentialDescriptor> allowed_list; + allowed_list.push_back(PublicKeyCredentialDescriptor( + CredentialType::kPublicKey, + fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle))); + request.SetAlternativeApplicationParameter( + test_data::kAlternativeApplicationParameter); + request.SetAllowList(std::move(allowed_list)); + + ::testing::InSequence s; auto device = MockFidoDevice::MakeCtap(); - device->ExpectCtap2CommandAndRespondWith( - CtapRequestCommand::kAuthenticatorGetAssertion, base::nullopt); + std::array<uint8_t, 1> error{{base::strict_cast<uint8_t>( + CtapDeviceResponseCode::kCtap2ErrNoCredentials)}}; + // First as device supports both CTAP and U2F protocol, browser will try CTAP + // GetAssertion. + device->ExpectRequestAndRespondWith(test_data::kCtapSilentGetAssertionRequest, + error); + // After falling back to U2F request will use the primary app_param, which + // will be rejected. + device->ExpectRequestAndRespondWith(test_data::kU2fCheckOnlySignCommandApdu, + test_data::kU2fWrongDataApduResponse); + // After the rejection, the U2F sign request with alternative application + // parameter should be tried. + device->ExpectRequestAndRespondWith( + test_data:: + kU2fCheckOnlySignCommandApduWithAlternativeApplicationParameter, + test_data::kApduEncodedNoErrorSignResponse); + device->ExpectRequestAndRespondWith( + test_data::kU2fSignCommandApduWithAlternativeApplicationParameter, + test_data::kApduEncodedNoErrorSignResponse); auto task = std::make_unique<GetAssertionTask>( - device.get(), - CtapGetAssertionRequest(test_data::kRelyingPartyId, - test_data::kClientDataHash), + device.get(), std::move(request), get_assertion_callback_receiver().callback()); - get_assertion_callback_receiver().WaitForCallback(); - EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrOther, + EXPECT_EQ(CtapDeviceResponseCode::kSuccess, get_assertion_callback_receiver().status()); - EXPECT_FALSE(get_assertion_callback_receiver().value()); } -TEST_F(FidoGetAssertionTaskTest, TestU2fSignRequestWithEmptyAllowedList) { - auto request = CtapGetAssertionRequest(test_data::kRelyingPartyId, - test_data::kClientDataHash); +TEST_F(FidoGetAssertionTaskTest, TestAvoidSilentSignInForCtapOnlyDevice) { + CtapGetAssertionRequest request(test_data::kRelyingPartyId, + test_data::kClientDataHash); - auto device = MockFidoDevice::MakeU2f(); - device->ExpectRequestAndRespondWith( - test_data::kU2fFakeRegisterCommand, - test_data::kApduEncodedNoErrorSignResponse); + std::vector<PublicKeyCredentialDescriptor> allowed_list; + allowed_list.push_back(PublicKeyCredentialDescriptor( + CredentialType::kPublicKey, + fido_parsing_utils::Materialize(test_data::kU2fSignKeyHandle))); + + request.SetAlternativeApplicationParameter( + test_data::kAlternativeApplicationParameter); + request.SetAllowList(std::move(allowed_list)); + + auto device = MockFidoDevice::MakeCtap(ReadCTAPGetInfoResponse( + test_data::kTestCtap2OnlyAuthenticatorGetInfoResponse)); + std::array<uint8_t, 1> error{ + {base::strict_cast<uint8_t>(CtapDeviceResponseCode::kCtap2ErrOther)}}; + device->ExpectRequestAndRespondWith(test_data::kCtapGetAssertionRequest, + error); auto task = std::make_unique<GetAssertionTask>( device.get(), std::move(request), get_assertion_callback_receiver().callback()); - get_assertion_callback_receiver().WaitForCallback(); - EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrNoCredentials, + EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrOther, get_assertion_callback_receiver().status()); - EXPECT_FALSE(get_assertion_callback_receiver().value()); } } // namespace diff --git a/chromium/device/fido/fake_hid_impl_for_testing.cc b/chromium/device/fido/hid/fake_hid_impl_for_testing.cc index dc2a5d56db8..cf86174175a 100644 --- a/chromium/device/fido/fake_hid_impl_for_testing.cc +++ b/chromium/device/fido/hid/fake_hid_impl_for_testing.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fake_hid_impl_for_testing.h" +#include "device/fido/hid/fake_hid_impl_for_testing.h" #include <utility> diff --git a/chromium/device/fido/fake_hid_impl_for_testing.h b/chromium/device/fido/hid/fake_hid_impl_for_testing.h index 552adde891b..b69945adc0c 100644 --- a/chromium/device/fido/fake_hid_impl_for_testing.h +++ b/chromium/device/fido/hid/fake_hid_impl_for_testing.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef DEVICE_FIDO_FAKE_HID_IMPL_FOR_TESTING_H_ -#define DEVICE_FIDO_FAKE_HID_IMPL_FOR_TESTING_H_ +#ifndef DEVICE_FIDO_HID_FAKE_HID_IMPL_FOR_TESTING_H_ +#define DEVICE_FIDO_HID_FAKE_HID_IMPL_FOR_TESTING_H_ #include <map> #include <string> @@ -120,4 +120,4 @@ class FakeHidManager : public device::mojom::HidManager { } // namespace device -#endif // DEVICE_FIDO_FAKE_HID_IMPL_FOR_TESTING_H_ +#endif // DEVICE_FIDO_HID_FAKE_HID_IMPL_FOR_TESTING_H_ diff --git a/chromium/device/fido/fido_hid_device.cc b/chromium/device/fido/hid/fido_hid_device.cc index 5dee08aacc9..9734a739218 100644 --- a/chromium/device/fido/fido_hid_device.cc +++ b/chromium/device/fido/hid/fido_hid_device.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fido_hid_device.h" +#include "device/fido/hid/fido_hid_device.h" #include "base/bind.h" #include "base/bind_helpers.h" @@ -10,7 +10,7 @@ #include "base/logging.h" #include "base/threading/thread_task_runner_handle.h" #include "crypto/random.h" -#include "device/fido/fido_hid_message.h" +#include "device/fido/hid/fido_hid_message.h" #include "mojo/public/cpp/bindings/interface_request.h" namespace device { @@ -143,7 +143,7 @@ void FidoHidDevice::OnAllocateChannel(std::vector<uint8_t> nonce, timeout_callback_.Cancel(); - if (!message) { + if (!message || message->cmd() != FidoHidDeviceCommand::kInit) { state_ = State::kDeviceError; Transition(std::vector<uint8_t>(), std::move(callback)); return; @@ -217,6 +217,7 @@ void FidoHidDevice::PacketWritten(base::Optional<FidoHidMessage> message, void FidoHidDevice::ReadMessage(HidMessageCallback callback) { if (!connection_) { + state_ = State::kDeviceError; std::move(callback).Run(base::nullopt); return; } @@ -230,6 +231,7 @@ void FidoHidDevice::OnRead(HidMessageCallback callback, uint8_t report_id, const base::Optional<std::vector<uint8_t>>& buf) { if (!success) { + state_ = State::kDeviceError; std::move(callback).Run(base::nullopt); return; } @@ -267,6 +269,7 @@ void FidoHidDevice::OnReadContinuation( uint8_t report_id, const base::Optional<std::vector<uint8_t>>& buf) { if (!success) { + state_ = State::kDeviceError; std::move(callback).Run(base::nullopt); return; } @@ -307,8 +310,7 @@ void FidoHidDevice::MessageReceived(DeviceCallback callback, } if (cmd != FidoHidDeviceCommand::kMsg && cmd != FidoHidDeviceCommand::kCbor) { - DLOG(ERROR) << "Unexpected HID device command received."; - state_ = State::kDeviceError; + ProcessHidError(cmd, message->GetMessagePayload()); Transition(std::vector<uint8_t>(), std::move(callback)); return; } @@ -373,10 +375,35 @@ void FidoHidDevice::OnTimeout(DeviceCallback callback) { Transition(std::vector<uint8_t>(), std::move(callback)); } +void FidoHidDevice::ProcessHidError(FidoHidDeviceCommand cmd, + base::span<const uint8_t> payload) { + if (cmd != FidoHidDeviceCommand::kError || payload.size() != 1) { + DLOG(ERROR) << "Unexpected HID device command received."; + state_ = State::kDeviceError; + return; + } + + const auto error_constant = payload[0]; + if (error_constant == + base::strict_cast<uint8_t>(HidErrorConstant::kInvalidCommand) || + error_constant == + base::strict_cast<uint8_t>(HidErrorConstant::kInvalidParameter) || + error_constant == + base::strict_cast<uint8_t>(HidErrorConstant::kInvalidLength)) { + state_ = State::kMsgError; + } else { + state_ = State::kDeviceError; + } +} + std::string FidoHidDevice::GetId() const { return GetIdForDevice(*device_info_); } +FidoTransportProtocol FidoHidDevice::DeviceTransport() const { + return FidoTransportProtocol::kUsbHumanInterfaceDevice; +} + // static std::string FidoHidDevice::GetIdForDevice( const device::mojom::HidDeviceInfo& device_info) { diff --git a/chromium/device/fido/fido_hid_device.h b/chromium/device/fido/hid/fido_hid_device.h index 10f34023244..64a2ac9bee7 100644 --- a/chromium/device/fido/fido_hid_device.h +++ b/chromium/device/fido/hid/fido_hid_device.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef DEVICE_FIDO_FIDO_HID_DEVICE_H_ -#define DEVICE_FIDO_FIDO_HID_DEVICE_H_ +#ifndef DEVICE_FIDO_HID_FIDO_HID_DEVICE_H_ +#define DEVICE_FIDO_HID_FIDO_HID_DEVICE_H_ #include <string> #include <utility> @@ -26,6 +26,21 @@ class FidoHidMessage; class COMPONENT_EXPORT(DEVICE_FIDO) FidoHidDevice : public FidoDevice { public: + // HID transport layer error constants that are returned to the client. + // Carried in the payload section of the Error command. + // https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#ctaphid-commands + enum class HidErrorConstant : uint8_t { + kInvalidCommand = 0x01, + kInvalidParameter = 0x02, + kInvalidLength = 0x03, + kInvalidSequence = 0x04, + kTimeout = 0x05, + kBusy = 0x06, + kLockRequired = 0x0a, + kInvalidChannel = 0x0b, + kOther = 0x7f, + }; + FidoHidDevice(device::mojom::HidDeviceInfoPtr device_info, device::mojom::HidManager* hid_manager); ~FidoHidDevice() final; @@ -41,6 +56,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoHidDevice : public FidoDevice { void Cancel() final; // Use a string identifier to compare to other devices. std::string GetId() const final; + FidoTransportProtocol DeviceTransport() const final; // Get a string identifier for a given device info. static std::string GetIdForDevice( @@ -97,6 +113,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoHidDevice : public FidoDevice { void OnWink(WinkCallback callback, base::Optional<FidoHidMessage> response); void ArmTimeout(DeviceCallback callback); void OnTimeout(DeviceCallback callback); + void ProcessHidError(FidoHidDeviceCommand cmd, + base::span<const uint8_t> payload); base::WeakPtr<FidoDevice> GetWeakPtr() override; @@ -120,4 +138,4 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoHidDevice : public FidoDevice { } // namespace device -#endif // DEVICE_FIDO_FIDO_HID_DEVICE_H_ +#endif // DEVICE_FIDO_HID_FIDO_HID_DEVICE_H_ diff --git a/chromium/device/fido/fido_hid_device_unittest.cc b/chromium/device/fido/hid/fido_hid_device_unittest.cc index 88c86732eb7..0942bf14226 100644 --- a/chromium/device/fido/fido_hid_device_unittest.cc +++ b/chromium/device/fido/hid/fido_hid_device_unittest.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fido_hid_device.h" +#include "device/fido/hid/fido_hid_device.h" #include <memory> #include <tuple> @@ -14,9 +14,9 @@ #include "base/strings/string_number_conversions.h" #include "base/test/scoped_task_environment.h" #include "base/threading/thread_task_runner_handle.h" -#include "device/fido/fake_hid_impl_for_testing.h" #include "device/fido/fido_constants.h" #include "device/fido/fido_parsing_utils.h" +#include "device/fido/hid/fake_hid_impl_for_testing.h" #include "device/fido/test_callback_receiver.h" #include "mojo/public/cpp/bindings/binding.h" #include "mojo/public/cpp/bindings/interface_request.h" @@ -108,6 +108,38 @@ device::mojom::HidDeviceInfoPtr TestHidDevice() { return hid_device; } +std::unique_ptr<MockHidConnection> CreateHidConnectionWithHidInitExpectations( + base::span<const uint8_t> channel_id, + FakeHidManager* fake_hid_manager, + ::testing::Sequence sequence) { + auto hid_device = TestHidDevice(); + device::mojom::HidConnectionPtr connection_client; + + // Replace device HID connection with custom client connection bound to mock + // server-side mojo connection. + auto mock_connection = std::make_unique<MockHidConnection>( + hid_device.Clone(), mojo::MakeRequest(&connection_client), + fido_parsing_utils::Materialize(channel_id)); + + // Initial write for establishing channel ID. + mock_connection->ExpectWriteHidInit(); + + EXPECT_CALL(*mock_connection, ReadPtr(_)) + .InSequence(sequence) + // Response to HID_INIT request. + .WillOnce(Invoke([&](device::mojom::HidConnection::ReadCallback* cb) { + std::move(*cb).Run( + true, 0, + CreateMockInitResponse(mock_connection->nonce(), + mock_connection->connection_channel_id())); + })); + + // Add device and set mock connection to fake hid manager. + fake_hid_manager->AddDeviceAndSetConnection(std::move(hid_device), + std::move(connection_client)); + return mock_connection; +} + class FidoDeviceEnumerateCallbackReceiver : public test::TestCallbackReceiver<std::vector<mojom::HidDeviceInfoPtr>> { public: @@ -256,13 +288,12 @@ TEST_F(FidoHidDeviceTest, TestRetryChannelAllocation) { EXPECT_CALL(mock_connection, ReadPtr(_)) // First response to HID_INIT request with an incorrect nonce. - .WillOnce( - Invoke([kIncorrectNonce, &mock_connection](auto* cb) { - std::move(*cb).Run( - true, 0, - CreateMockInitResponse( - kIncorrectNonce, mock_connection.connection_channel_id())); - })) + .WillOnce(Invoke([kIncorrectNonce, &mock_connection](auto* cb) { + std::move(*cb).Run( + true, 0, + CreateMockInitResponse(kIncorrectNonce, + mock_connection.connection_channel_id())); + })) // Second response to HID_INIT request with a correct nonce. .WillOnce(Invoke( [&mock_connection](device::mojom::HidConnection::ReadCallback* cb) { @@ -303,35 +334,20 @@ TEST_F(FidoHidDeviceTest, TestRetryChannelAllocation) { TEST_F(FidoHidDeviceTest, TestKeepAliveMessage) { constexpr uint8_t kChannelId[] = {0x01, 0x02, 0x03, 0x04}; - - auto hid_device = TestHidDevice(); - - // Replace device HID connection with custom client connection bound to mock - // server-side mojo connection. - device::mojom::HidConnectionPtr connection_client; - MockHidConnection mock_connection( - hid_device.Clone(), mojo::MakeRequest(&connection_client), - fido_parsing_utils::Materialize(kChannelId)); - - // Initial write for establishing channel ID. - mock_connection.ExpectWriteHidInit(); + ::testing::Sequence sequence; + auto mock_connection = CreateHidConnectionWithHidInitExpectations( + kChannelId, fake_hid_manager_.get(), sequence); // HID_CBOR request to authenticator. - mock_connection.ExpectHidWriteWithCommand(FidoHidDeviceCommand::kCbor); + mock_connection->ExpectHidWriteWithCommand(FidoHidDeviceCommand::kCbor); - EXPECT_CALL(mock_connection, ReadPtr(_)) - // Response to HID_INIT request. + EXPECT_CALL(*mock_connection, ReadPtr(_)) + .InSequence(sequence) + // Keep alive message sent from the authenticator. .WillOnce(Invoke([&](device::mojom::HidConnection::ReadCallback* cb) { std::move(*cb).Run( true, 0, - CreateMockInitResponse(mock_connection.nonce(), - mock_connection.connection_channel_id())); - })) - // // Keep alive message sent from the authenticator. - .WillOnce(Invoke([&](device::mojom::HidConnection::ReadCallback* cb) { - std::move(*cb).Run( - true, 0, - GetKeepAliveHidMessage(mock_connection.connection_channel_id())); + GetKeepAliveHidMessage(mock_connection->connection_channel_id())); })) // Repeated Read() invocation due to keep alive message. Sends a dummy // response that corresponds to U2F version response. @@ -342,14 +358,10 @@ TEST_F(FidoHidDeviceTest, TestKeepAliveMessage) { std::move(*cb).Run(true, 0, CreateMockResponseWithChannelId( - mock_connection.connection_channel_id(), + mock_connection->connection_channel_id(), kU2fMockResponseMessage)); })); - // Add device and set mock connection to fake hid manager. - fake_hid_manager_->AddDeviceAndSetConnection(std::move(hid_device), - std::move(connection_client)); - FidoDeviceEnumerateCallbackReceiver receiver(hid_manager_.get()); hid_manager_->GetDevices(receiver.callback()); receiver.WaitForCallback(); @@ -371,35 +383,20 @@ TEST_F(FidoHidDeviceTest, TestKeepAliveMessage) { TEST_F(FidoHidDeviceTest, TestDeviceTimeoutAfterKeepAliveMessage) { constexpr uint8_t kChannelId[] = {0x01, 0x02, 0x03, 0x04}; - - auto hid_device = TestHidDevice(); - - // Replace device HID connection with custom client connection bound to mock - // server-side mojo connection. - device::mojom::HidConnectionPtr connection_client; - MockHidConnection mock_connection( - hid_device.Clone(), mojo::MakeRequest(&connection_client), - fido_parsing_utils::Materialize(kChannelId)); - - // Initial write for establishing channel ID. - mock_connection.ExpectWriteHidInit(); + ::testing::Sequence sequence; + auto mock_connection = CreateHidConnectionWithHidInitExpectations( + kChannelId, fake_hid_manager_.get(), sequence); // HID_CBOR request to authenticator. - mock_connection.ExpectHidWriteWithCommand(FidoHidDeviceCommand::kCbor); + mock_connection->ExpectHidWriteWithCommand(FidoHidDeviceCommand::kCbor); - EXPECT_CALL(mock_connection, ReadPtr(_)) - // Response to HID_INIT request. - .WillOnce(Invoke([&](device::mojom::HidConnection::ReadCallback* cb) { - std::move(*cb).Run( - true, 0, - CreateMockInitResponse(mock_connection.nonce(), - mock_connection.connection_channel_id())); - })) - // // Keep alive message sent from the authenticator. + EXPECT_CALL(*mock_connection, ReadPtr(_)) + .InSequence(sequence) + // Keep alive message sent from the authenticator. .WillOnce(Invoke([&](device::mojom::HidConnection::ReadCallback* cb) { std::move(*cb).Run( true, 0, - GetKeepAliveHidMessage(mock_connection.connection_channel_id())); + GetKeepAliveHidMessage(mock_connection->connection_channel_id())); })) // Repeated Read() invocation due to keep alive message. The callback // is invoked only after 3 seconds, which should cause device to timeout. @@ -407,14 +404,10 @@ TEST_F(FidoHidDeviceTest, TestDeviceTimeoutAfterKeepAliveMessage) { scoped_task_environment_.FastForwardBy(kDeviceTimeout); std::move(*cb).Run(true, 0, CreateMockResponseWithChannelId( - mock_connection.connection_channel_id(), + mock_connection->connection_channel_id(), kU2fMockResponseMessage)); })); - // Add device and set mock connection to fake hid manager. - fake_hid_manager_->AddDeviceAndSetConnection(std::move(hid_device), - std::move(connection_client)); - FidoDeviceEnumerateCallbackReceiver receiver(hid_manager_.get()); hid_manager_->GetDevices(receiver.callback()); receiver.WaitForCallback(); @@ -436,33 +429,18 @@ TEST_F(FidoHidDeviceTest, TestDeviceTimeoutAfterKeepAliveMessage) { TEST_F(FidoHidDeviceTest, TestCancel) { constexpr uint8_t kChannelId[] = {0x01, 0x02, 0x03, 0x04}; - - auto hid_device = TestHidDevice(); - - // Replace device HID connection with custom client connection bound to mock - // server-side mojo connection. - device::mojom::HidConnectionPtr connection_client; - MockHidConnection mock_connection( - hid_device.Clone(), mojo::MakeRequest(&connection_client), - fido_parsing_utils::Materialize(kChannelId)); - - // Initial write for establishing channel ID. - mock_connection.ExpectWriteHidInit(); + ::testing::Sequence sequence; + auto mock_connection = CreateHidConnectionWithHidInitExpectations( + kChannelId, fake_hid_manager_.get(), sequence); // HID_CBOR request to authenticator. - mock_connection.ExpectHidWriteWithCommand(FidoHidDeviceCommand::kCbor); + mock_connection->ExpectHidWriteWithCommand(FidoHidDeviceCommand::kCbor); // Cancel request to authenticator. - mock_connection.ExpectHidWriteWithCommand(FidoHidDeviceCommand::kCancel); + mock_connection->ExpectHidWriteWithCommand(FidoHidDeviceCommand::kCancel); - EXPECT_CALL(mock_connection, ReadPtr(_)) - // Response to HID_INIT request. - .WillOnce(Invoke([&](device::mojom::HidConnection::ReadCallback* cb) { - std::move(*cb).Run( - true, 0, - CreateMockInitResponse(mock_connection.nonce(), - mock_connection.connection_channel_id())); - })) + EXPECT_CALL(*mock_connection, ReadPtr(_)) + .InSequence(sequence) // Device response with a significant delay. .WillOnce(Invoke([&](device::mojom::HidConnection::ReadCallback* cb) { auto delay = base::TimeDelta::FromSeconds(2); @@ -470,15 +448,11 @@ TEST_F(FidoHidDeviceTest, TestCancel) { FROM_HERE, base::BindOnce(std::move(*cb), true, 0, CreateMockResponseWithChannelId( - mock_connection.connection_channel_id(), + mock_connection->connection_channel_id(), kU2fMockResponseMessage)), delay); })); - // Add device and set mock connection to fake hid manager. - fake_hid_manager_->AddDeviceAndSetConnection(std::move(hid_device), - std::move(connection_client)); - FidoDeviceEnumerateCallbackReceiver receiver(hid_manager_.get()); hid_manager_->GetDevices(receiver.callback()); receiver.WaitForCallback(); @@ -500,4 +474,88 @@ TEST_F(FidoHidDeviceTest, TestCancel) { scoped_task_environment_.FastForwardUntilNoTasksRemain(); } +TEST_F(FidoHidDeviceTest, TestGetInfoFailsOnDeviceError) { + constexpr uint8_t kChannelId[] = {0x01, 0x02, 0x03, 0x04}; + // HID_ERROR(7F), followed by payload length(0001), followed by kUnknown(7F). + constexpr uint8_t kHidUnknownTransportError[] = {0x7F, 0x00, 0x01, 0x7F}; + ::testing::Sequence sequence; + auto mock_connection = CreateHidConnectionWithHidInitExpectations( + kChannelId, fake_hid_manager_.get(), sequence); + + // HID_CBOR request to authenticator. + mock_connection->ExpectHidWriteWithCommand(FidoHidDeviceCommand::kCbor); + + EXPECT_CALL(*mock_connection, ReadPtr(_)) + .InSequence(sequence) + // Device response with a significant delay. + .WillOnce(Invoke([&](device::mojom::HidConnection::ReadCallback* cb) { + auto delay = base::TimeDelta::FromSeconds(2); + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce(std::move(*cb), true, 0, + CreateMockResponseWithChannelId( + mock_connection->connection_channel_id(), + kHidUnknownTransportError)), + delay); + })); + + FidoDeviceEnumerateCallbackReceiver receiver(hid_manager_.get()); + hid_manager_->GetDevices(receiver.callback()); + receiver.WaitForCallback(); + + std::vector<std::unique_ptr<FidoHidDevice>> u2f_devices = + receiver.TakeReturnedDevicesFiltered(); + ASSERT_EQ(1u, u2f_devices.size()); + auto& device = u2f_devices.front(); + + device::test::TestCallbackReceiver<> get_info_callback; + device->DiscoverSupportedProtocolAndDeviceInfo(get_info_callback.callback()); + scoped_task_environment_.FastForwardUntilNoTasksRemain(); + EXPECT_FALSE(get_info_callback.was_called()); + EXPECT_EQ(FidoDevice::State::kDeviceError, device->state()); +} + +// Test that FidoHidDevice::DiscoverSupportedProtocolAndDeviceInfo() invokes +// callback when device error outs with kMsgError state. +TEST_F(FidoHidDeviceTest, TestDeviceMessageError) { + constexpr uint8_t kChannelId[] = {0x01, 0x02, 0x03, 0x04}; + // HID_ERROR(BF), followed by payload length(0001), followed by + // kInvalidCommand(01). + constexpr uint8_t kHidUnknownCommandError[] = {0xBF, 0x00, 0x01, 0x01}; + ::testing::Sequence sequence; + auto mock_connection = CreateHidConnectionWithHidInitExpectations( + kChannelId, fake_hid_manager_.get(), sequence); + + // HID_CBOR request to authenticator. + mock_connection->ExpectHidWriteWithCommand(FidoHidDeviceCommand::kCbor); + + EXPECT_CALL(*mock_connection, ReadPtr(_)) + .InSequence(sequence) + // Device response with a significant delay. + .WillOnce(Invoke([&](device::mojom::HidConnection::ReadCallback* cb) { + auto delay = base::TimeDelta::FromSeconds(2); + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce(std::move(*cb), true, 0, + CreateMockResponseWithChannelId( + mock_connection->connection_channel_id(), + kHidUnknownCommandError)), + delay); + })); + + FidoDeviceEnumerateCallbackReceiver receiver(hid_manager_.get()); + hid_manager_->GetDevices(receiver.callback()); + receiver.WaitForCallback(); + + std::vector<std::unique_ptr<FidoHidDevice>> u2f_devices = + receiver.TakeReturnedDevicesFiltered(); + ASSERT_EQ(1u, u2f_devices.size()); + auto& device = u2f_devices.front(); + + device::test::TestCallbackReceiver<> get_info_callback; + device->DiscoverSupportedProtocolAndDeviceInfo(get_info_callback.callback()); + scoped_task_environment_.FastForwardUntilNoTasksRemain(); + EXPECT_TRUE(get_info_callback.was_called()); +} + } // namespace device diff --git a/chromium/device/fido/fido_hid_discovery.cc b/chromium/device/fido/hid/fido_hid_discovery.cc index 483b4b35e64..b1eb519d9f7 100644 --- a/chromium/device/fido/fido_hid_discovery.cc +++ b/chromium/device/fido/hid/fido_hid_discovery.cc @@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fido_hid_discovery.h" +#include "device/fido/hid/fido_hid_discovery.h" #include <utility> -#include "device/fido/fido_hid_device.h" +#include "device/fido/hid/fido_hid_device.h" #include "mojo/public/cpp/bindings/interface_request.h" #include "services/device/public/mojom/constants.mojom.h" #include "services/service_manager/public/cpp/connector.h" @@ -57,6 +57,7 @@ void FidoHidDiscovery::OnGetDevices( std::vector<device::mojom::HidDeviceInfoPtr> device_infos) { for (auto& device_info : device_infos) DeviceAdded(std::move(device_info)); + NotifyDiscoveryStarted(true); } diff --git a/chromium/device/fido/fido_hid_discovery.h b/chromium/device/fido/hid/fido_hid_discovery.h index 94f70426a3a..c6a5e788063 100644 --- a/chromium/device/fido/fido_hid_discovery.h +++ b/chromium/device/fido/hid/fido_hid_discovery.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef DEVICE_FIDO_FIDO_HID_DISCOVERY_H_ -#define DEVICE_FIDO_FIDO_HID_DISCOVERY_H_ +#ifndef DEVICE_FIDO_HID_FIDO_HID_DISCOVERY_H_ +#define DEVICE_FIDO_HID_FIDO_HID_DISCOVERY_H_ #include <memory> #include <vector> @@ -53,4 +53,4 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoHidDiscovery } // namespace device -#endif // DEVICE_FIDO_FIDO_HID_DISCOVERY_H_ +#endif // DEVICE_FIDO_HID_FIDO_HID_DISCOVERY_H_ diff --git a/chromium/device/fido/fido_hid_discovery_unittest.cc b/chromium/device/fido/hid/fido_hid_discovery_unittest.cc index 7f1cff5ea9c..75c1c70c60f 100644 --- a/chromium/device/fido/fido_hid_discovery_unittest.cc +++ b/chromium/device/fido/hid/fido_hid_discovery_unittest.cc @@ -2,14 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fido_hid_discovery.h" +#include "device/fido/hid/fido_hid_discovery.h" #include <string> #include <utility> #include "base/test/scoped_task_environment.h" -#include "device/fido/fake_hid_impl_for_testing.h" -#include "device/fido/fido_hid_device.h" +#include "device/fido/hid/fake_hid_impl_for_testing.h" +#include "device/fido/hid/fido_hid_device.h" #include "device/fido/mock_fido_discovery_observer.h" #include "services/device/public/mojom/constants.mojom.h" #include "services/device/public/mojom/hid.mojom.h" diff --git a/chromium/device/fido/fido_hid_message.cc b/chromium/device/fido/hid/fido_hid_message.cc index d95598c4a62..3040696c26d 100644 --- a/chromium/device/fido/fido_hid_message.cc +++ b/chromium/device/fido/hid/fido_hid_message.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fido_hid_message.h" +#include "device/fido/hid/fido_hid_message.h" #include <algorithm> #include <numeric> diff --git a/chromium/device/fido/fido_hid_message.h b/chromium/device/fido/hid/fido_hid_message.h index ecf386cd39e..715c68e800e 100644 --- a/chromium/device/fido/fido_hid_message.h +++ b/chromium/device/fido/hid/fido_hid_message.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef DEVICE_FIDO_FIDO_HID_MESSAGE_H_ -#define DEVICE_FIDO_FIDO_HID_MESSAGE_H_ +#ifndef DEVICE_FIDO_HID_FIDO_HID_MESSAGE_H_ +#define DEVICE_FIDO_HID_FIDO_HID_MESSAGE_H_ #include <stddef.h> #include <stdint.h> @@ -19,7 +19,7 @@ #include "base/macros.h" #include "base/optional.h" #include "device/fido/fido_constants.h" -#include "device/fido/fido_hid_packet.h" +#include "device/fido/hid/fido_hid_packet.h" namespace device { @@ -72,4 +72,4 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoHidMessage { } // namespace device -#endif // DEVICE_FIDO_FIDO_HID_MESSAGE_H_ +#endif // DEVICE_FIDO_HID_FIDO_HID_MESSAGE_H_ diff --git a/chromium/device/fido/fido_hid_message_fuzzer.cc b/chromium/device/fido/hid/fido_hid_message_fuzzer.cc index 4357cb455b1..8b267c82901 100644 --- a/chromium/device/fido/fido_hid_message_fuzzer.cc +++ b/chromium/device/fido/hid/fido_hid_message_fuzzer.cc @@ -9,7 +9,7 @@ #include <vector> #include "base/containers/span.h" -#include "device/fido/fido_hid_message.h" +#include "device/fido/hid/fido_hid_message.h" namespace device { diff --git a/chromium/device/fido/fido_hid_message_unittest.cc b/chromium/device/fido/hid/fido_hid_message_unittest.cc index b819454dbe5..610af543756 100644 --- a/chromium/device/fido/fido_hid_message_unittest.cc +++ b/chromium/device/fido/hid/fido_hid_message_unittest.cc @@ -2,12 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fido_hid_message.h" +#include "device/fido/hid/fido_hid_message.h" #include "base/memory/ptr_util.h" #include "base/numerics/safe_conversions.h" #include "device/fido/fido_constants.h" -#include "device/fido/fido_hid_packet.h" +#include "device/fido/hid/fido_hid_packet.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" diff --git a/chromium/device/fido/fido_hid_packet.cc b/chromium/device/fido/hid/fido_hid_packet.cc index 10a1cebb939..0024cddc91e 100644 --- a/chromium/device/fido/fido_hid_packet.cc +++ b/chromium/device/fido/hid/fido_hid_packet.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "device/fido/fido_hid_packet.h" +#include "device/fido/hid/fido_hid_packet.h" #include <algorithm> #include <utility> diff --git a/chromium/device/fido/fido_hid_packet.h b/chromium/device/fido/hid/fido_hid_packet.h index 128f1ed2e35..2c3fbbc0b13 100644 --- a/chromium/device/fido/fido_hid_packet.h +++ b/chromium/device/fido/hid/fido_hid_packet.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef DEVICE_FIDO_FIDO_HID_PACKET_H_ -#define DEVICE_FIDO_FIDO_HID_PACKET_H_ +#ifndef DEVICE_FIDO_HID_FIDO_HID_PACKET_H_ +#define DEVICE_FIDO_HID_FIDO_HID_PACKET_H_ #include <stddef.h> #include <stdint.h> diff --git a/chromium/device/fido/mac/OWNERS b/chromium/device/fido/mac/OWNERS new file mode 100644 index 00000000000..eac9cee5ed5 --- /dev/null +++ b/chromium/device/fido/mac/OWNERS @@ -0,0 +1,5 @@ +martinkr@chromium.org +martinkr@google.com + +# TEAM: security-dev@chromium.org +# COMPONENT: Blink>WebAuthentication diff --git a/chromium/device/fido/mac/authenticator.h b/chromium/device/fido/mac/authenticator.h index 5030f64508a..090e643bb57 100644 --- a/chromium/device/fido/mac/authenticator.h +++ b/chromium/device/fido/mac/authenticator.h @@ -5,11 +5,16 @@ #ifndef DEVICE_FIDO_MAC_AUTHENTICATOR_H_ #define DEVICE_FIDO_MAC_AUTHENTICATOR_H_ +#include <memory> +#include <string> + #include "base/component_export.h" #include "base/mac/availability.h" #include "base/macros.h" +#include "base/memory/weak_ptr.h" #include "base/strings/string_piece_forward.h" #include "device/fido/fido_authenticator.h" +#include "device/fido/fido_transport_protocol.h" #include "device/fido/mac/operation.h" namespace device { @@ -21,6 +26,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) TouchIdAuthenticator public: // IsAvailable returns whether Touch ID is available and enrolled on the // current device. + // + // Note that this may differ from the result of + // AuthenticatorImpl::IsUserVerifyingPlatformAuthenticatorAvailable, which + // also checks whether the embedder supports this authenticator, and if the + // request occurs from an off-the-record/incognito context. static bool IsAvailable(); // CreateIfAvailable returns a TouchIdAuthenticator if IsAvailable() returns @@ -35,15 +45,20 @@ class COMPONENT_EXPORT(DEVICE_FIDO) TouchIdAuthenticator ~TouchIdAuthenticator() override; + bool HasCredentialForGetAssertionRequest( + const CtapGetAssertionRequest& request); + // FidoAuthenticator - void MakeCredential( - CtapMakeCredentialRequest request, - MakeCredentialCallback callback) override; + void InitializeAuthenticator(base::OnceClosure callback) override; + void MakeCredential(CtapMakeCredentialRequest request, + MakeCredentialCallback callback) override; void GetAssertion(CtapGetAssertionRequest request, GetAssertionCallback callback) override; void Cancel() override; std::string GetId() const override; const AuthenticatorSupportedOptions& Options() const override; + FidoTransportProtocol AuthenticatorTransport() const override; + base::WeakPtr<FidoAuthenticator> GetWeakPtr() override; private: TouchIdAuthenticator(std::string keychain_access_group, @@ -61,6 +76,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) TouchIdAuthenticator std::unique_ptr<Operation> operation_; + base::WeakPtrFactory<TouchIdAuthenticator> weak_factory_; + private: DISALLOW_COPY_AND_ASSIGN(TouchIdAuthenticator); }; diff --git a/chromium/device/fido/mac/authenticator.mm b/chromium/device/fido/mac/authenticator.mm index 76f9d482d58..b5e03ce84d4 100644 --- a/chromium/device/fido/mac/authenticator.mm +++ b/chromium/device/fido/mac/authenticator.mm @@ -4,13 +4,18 @@ #include "device/fido/mac/authenticator.h" +#include <algorithm> + #import <LocalAuthentication/LocalAuthentication.h> +#include "base/bind.h" #include "base/feature_list.h" #include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/optional.h" +#include "base/stl_util.h" #include "base/strings/string_piece.h" +#include "base/threading/sequenced_task_runner_handle.h" #include "device/base/features.h" #include "device/fido/authenticator_supported_options.h" #include "device/fido/ctap_get_assertion_request.h" @@ -28,10 +33,7 @@ namespace mac { bool TouchIdAuthenticator::IsAvailable() { if (base::FeatureList::IsEnabled(device::kWebAuthTouchId)) { if (__builtin_available(macOS 10.12.2, *)) { - base::scoped_nsobject<LAContext> context([[LAContext alloc] init]); - return [context - canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics - error:nil]; + return TouchIdContext::TouchIdAvailable(); } } return false; @@ -58,6 +60,40 @@ std::unique_ptr<TouchIdAuthenticator> TouchIdAuthenticator::CreateForTesting( TouchIdAuthenticator::~TouchIdAuthenticator() = default; +bool TouchIdAuthenticator::HasCredentialForGetAssertionRequest( + const CtapGetAssertionRequest& request) { + if (__builtin_available(macOS 10.12.2, *)) { + std::set<std::vector<uint8_t>> allow_list_credential_ids; + // Extract applicable credential IDs from the allowList, if the request has + // one. If not, any credential matching the RP works. + if (request.allow_list()) { + for (const auto& credential_descriptor : *request.allow_list()) { + if (credential_descriptor.credential_type() != + CredentialType::kPublicKey) + continue; + + if (!credential_descriptor.transports().empty() && + !base::ContainsKey(credential_descriptor.transports(), + FidoTransportProtocol::kInternal)) + continue; + + allow_list_credential_ids.insert(credential_descriptor.id()); + } + } + + return FindCredentialInKeychain(keychain_access_group_, metadata_secret_, + request.rp_id(), allow_list_credential_ids, + nullptr /* LAContext */) != base::nullopt; + } + NOTREACHED(); + return false; +} + +void TouchIdAuthenticator::InitializeAuthenticator(base::OnceClosure callback) { + base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE, + std::move(callback)); +} + void TouchIdAuthenticator::MakeCredential(CtapMakeCredentialRequest request, MakeCredentialCallback callback) { if (__builtin_available(macOS 10.12.2, *)) { @@ -96,6 +132,10 @@ std::string TouchIdAuthenticator::GetId() const { return "TouchIdAuthenticator"; } +FidoTransportProtocol TouchIdAuthenticator::AuthenticatorTransport() const { + return FidoTransportProtocol::kInternal; +} + namespace { AuthenticatorSupportedOptions TouchIdAuthenticatorOptions() { @@ -117,10 +157,15 @@ const AuthenticatorSupportedOptions& TouchIdAuthenticator::Options() const { return options; } +base::WeakPtr<FidoAuthenticator> TouchIdAuthenticator::GetWeakPtr() { + return weak_factory_.GetWeakPtr(); +} + TouchIdAuthenticator::TouchIdAuthenticator(std::string keychain_access_group, std::string metadata_secret) : keychain_access_group_(std::move(keychain_access_group)), - metadata_secret_(std::move(metadata_secret)) {} + metadata_secret_(std::move(metadata_secret)), + weak_factory_(this) {} } // namespace mac } // namespace fido diff --git a/chromium/device/fido/mac/fake_keychain.h b/chromium/device/fido/mac/fake_keychain.h new file mode 100644 index 00000000000..97f275aaeb8 --- /dev/null +++ b/chromium/device/fido/mac/fake_keychain.h @@ -0,0 +1,57 @@ +// Copyright 2018 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. + +#ifndef DEVICE_FIDO_MAC_FAKE_KEYCHAIN_H_ +#define DEVICE_FIDO_MAC_FAKE_KEYCHAIN_H_ + +#include <string> +#include <vector> + +#include "base/mac/scoped_cftyperef.h" +#include "base/macros.h" +#include "device/fido/mac/keychain.h" + +namespace device { +namespace fido { +namespace mac { + +class API_AVAILABLE(macos(10.12.2)) FakeKeychain : public Keychain { + public: + struct Item { + Item(); + Item(Item&&); + Item& operator=(Item&&); + ~Item(); + + std::string label; + std::string application_label; + std::string application_tag; + base::ScopedCFTypeRef<SecKeyRef> private_key; + + private: + DISALLOW_COPY_AND_ASSIGN(Item); + }; + + FakeKeychain(); + ~FakeKeychain() override; + + protected: + // Keychain: + base::ScopedCFTypeRef<SecKeyRef> KeyCreateRandomKey( + CFDictionaryRef params, + CFErrorRef* error) override; + OSStatus ItemCopyMatching(CFDictionaryRef query, CFTypeRef* result) override; + OSStatus ItemDelete(CFDictionaryRef query) override; + + private: + std::vector<Item> items_; + + DISALLOW_COPY_AND_ASSIGN(FakeKeychain); +}; + +} // namespace mac +} // namespace fido +} // namespace device + +#endif // DEVICE_FIDO_MAC_FAKE_KEYCHAIN_H_ diff --git a/chromium/device/fido/mac/fake_keychain.mm b/chromium/device/fido/mac/fake_keychain.mm new file mode 100644 index 00000000000..1230468a89d --- /dev/null +++ b/chromium/device/fido/mac/fake_keychain.mm @@ -0,0 +1,46 @@ +// Copyright 2018 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. + +#import <Security/Security.h> + +#include "device/fido/mac/fake_keychain.h" + +#include "device/fido/mac/keychain.h" + +namespace device { +namespace fido { +namespace mac { + +FakeKeychain::FakeKeychain() = default; +FakeKeychain::~FakeKeychain() = default; + +FakeKeychain::Item::Item() = default; +FakeKeychain::Item::Item(Item&&) = default; +FakeKeychain::Item& FakeKeychain::Item::operator=(Item&&) = default; +FakeKeychain::Item::~Item() = default; + +base::ScopedCFTypeRef<SecKeyRef> FakeKeychain::KeyCreateRandomKey( + CFDictionaryRef parameters, + CFErrorRef* error) { + // TODO(martinkr): Implement. + NOTREACHED(); + return base::ScopedCFTypeRef<SecKeyRef>(); +} + +OSStatus FakeKeychain::ItemCopyMatching(CFDictionaryRef query, + CFTypeRef* result) { + // TODO(martinkr): Implement. + NOTREACHED(); + return errSecItemNotFound; +} + +OSStatus FakeKeychain::ItemDelete(CFDictionaryRef query) { + // TODO(martinkr): Implement. + NOTREACHED(); + return errSecItemNotFound; +} + +} // namespace mac +} // namespace fido +} // namespace device diff --git a/chromium/device/fido/mac/fake_touch_id_context.h b/chromium/device/fido/mac/fake_touch_id_context.h new file mode 100644 index 00000000000..28f552976f3 --- /dev/null +++ b/chromium/device/fido/mac/fake_touch_id_context.h @@ -0,0 +1,42 @@ +// Copyright 2018 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. + +#ifndef DEVICE_FIDO_MAC_FAKE_TOUCH_ID_CONTEXT_H_ +#define DEVICE_FIDO_MAC_FAKE_TOUCH_ID_CONTEXT_H_ + +#include "base/component_export.h" +#include "base/macros.h" +#include "device/fido/mac/touch_id_context.h" + +namespace device { +namespace fido { +namespace mac { + +class API_AVAILABLE(macosx(10.12.2)) FakeTouchIdContext + : public TouchIdContext { + public: + ~FakeTouchIdContext() override; + + // TouchIdContext: + void PromptTouchId(const base::string16& reason, Callback callback) override; + + void set_callback_result(bool callback_result) { + callback_result_ = callback_result; + } + + private: + friend class ScopedTouchIdTestEnvironment; + + FakeTouchIdContext(); + + bool callback_result_ = true; + + DISALLOW_COPY_AND_ASSIGN(FakeTouchIdContext); +}; + +} // namespace mac +} // namespace fido +} // namespace device + +#endif // DEVICE_FIDO_MAC_FAKE_TOUCH_ID_CONTEXT_H_ diff --git a/chromium/device/fido/mac/fake_touch_id_context.mm b/chromium/device/fido/mac/fake_touch_id_context.mm new file mode 100644 index 00000000000..ad9550426d7 --- /dev/null +++ b/chromium/device/fido/mac/fake_touch_id_context.mm @@ -0,0 +1,24 @@ +// Copyright 2018 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 "device/fido/mac/fake_touch_id_context.h" + +#include "base/bind.h" +#include "base/memory/ptr_util.h" + +namespace device { +namespace fido { +namespace mac { + +FakeTouchIdContext::FakeTouchIdContext() = default; +FakeTouchIdContext::~FakeTouchIdContext() = default; + +void FakeTouchIdContext::PromptTouchId(const base::string16& reason, + Callback callback) { + std::move(callback).Run(callback_result_); +} + +} // namespace mac +} // namespace fido +} // namespace device diff --git a/chromium/device/fido/mac/get_assertion_operation.h b/chromium/device/fido/mac/get_assertion_operation.h index 3146c8f9b77..d1ea2b5ce1d 100644 --- a/chromium/device/fido/mac/get_assertion_operation.h +++ b/chromium/device/fido/mac/get_assertion_operation.h @@ -10,6 +10,7 @@ #include "base/macros.h" #include "device/fido/authenticator_get_assertion_response.h" #include "device/fido/ctap_get_assertion_request.h" +#include "device/fido/mac/keychain.h" #include "device/fido/mac/operation_base.h" namespace device { diff --git a/chromium/device/fido/mac/get_assertion_operation.mm b/chromium/device/fido/mac/get_assertion_operation.mm index 4f1bec16d51..99fb012b428 100644 --- a/chromium/device/fido/mac/get_assertion_operation.mm +++ b/chromium/device/fido/mac/get_assertion_operation.mm @@ -12,12 +12,16 @@ #include "base/mac/foundation_util.h" #include "base/mac/mac_logging.h" #include "base/mac/scoped_cftyperef.h" +#include "base/stl_util.h" #include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" #include "device/fido/fido_constants.h" #include "device/fido/mac/keychain.h" #include "device/fido/mac/util.h" #include "device/fido/public_key_credential_descriptor.h" #include "device/fido/public_key_credential_user_entity.h" +#include "device/fido/strings/grit/fido_strings.h" +#include "ui/base/l10n/l10n_util.h" namespace device { namespace fido { @@ -47,9 +51,9 @@ void GetAssertionOperation::Run() { return; } - // Prompt the user for consent. - // TODO(martinkr): Localize reason strings. - PromptTouchId("sign in to " + RpId()); + // Display the macOS Touch ID prompt. + PromptTouchId(l10n_util::GetStringFUTF16(IDS_WEBAUTHN_TOUCH_ID_PROMPT_REASON, + base::UTF8ToUTF16(RpId()))); } void GetAssertionOperation::PromptTouchIdDone(bool success) { @@ -60,67 +64,31 @@ void GetAssertionOperation::PromptTouchIdDone(bool success) { } // Collect the credential ids from allowList. If allowList is absent, we will - // just pick the first available credential for the RP below. + // pick the first available credential for the RP. std::set<std::vector<uint8_t>> allowed_credential_ids; if (request().allow_list()) { for (const PublicKeyCredentialDescriptor& desc : *request().allow_list()) { - if (desc.credential_type() != CredentialType::kPublicKey) { + if (desc.credential_type() != CredentialType::kPublicKey) continue; - } + + if (!desc.transports().empty() && + !base::ContainsKey(desc.transports(), + FidoTransportProtocol::kInternal)) + continue; + allowed_credential_ids.insert(desc.id()); } } // Fetch credentials for RP from the request and current user profile. - ScopedCFTypeRef<CFArrayRef> keychain_items; - base::ScopedCFTypeRef<CFMutableDictionaryRef> query = DefaultKeychainQuery(); - CFDictionarySetValue(query, kSecUseAuthenticationContext, - authentication_context()); - CFDictionarySetValue(query, kSecReturnRef, @YES); - CFDictionarySetValue(query, kSecReturnAttributes, @YES); - CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitAll); - - OSStatus status = Keychain::GetInstance().ItemCopyMatching( - query, reinterpret_cast<CFTypeRef*>(keychain_items.InitializeInto())); - if (status == errSecItemNotFound) { - DVLOG(1) << "no credentials found for RP"; - std::move(callback()) - .Run(CtapDeviceResponseCode::kCtap2ErrNoCredentials, base::nullopt); - return; - } - if (status != errSecSuccess) { - OSSTATUS_DLOG(ERROR, status) << "SecItemCopyMatching failed"; - std::move(callback()) - .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt); - return; - } - SecKeyRef private_key = nil; // Owned by |keychain_items|. - std::vector<uint8_t> credential_id; - for (CFIndex i = 0; i < CFArrayGetCount(keychain_items); ++i) { - CFDictionaryRef attributes = base::mac::CFCast<CFDictionaryRef>( - CFArrayGetValueAtIndex(keychain_items, i)); - CFDataRef application_label = base::mac::GetValueFromDictionary<CFDataRef>( - attributes, kSecAttrApplicationLabel); - SecKeyRef key = - base::mac::GetValueFromDictionary<SecKeyRef>(attributes, kSecValueRef); - if (!application_label || !key) { - // Corrupted keychain? - DLOG(ERROR) << "could not find application label or key ref: " - << attributes; - continue; - } - std::vector<uint8_t> cid(CFDataGetBytePtr(application_label), - CFDataGetBytePtr(application_label) + - CFDataGetLength(application_label)); - if (allowed_credential_ids.empty() || - allowed_credential_ids.find(cid) != allowed_credential_ids.end()) { - private_key = key; - credential_id = std::move(cid); - break; - } - } - if (!private_key) { - DVLOG(1) << "no allowed credential found"; + base::Optional<Credential> credential = FindCredentialInKeychain( + keychain_access_group(), metadata_secret(), RpId(), + allowed_credential_ids, authentication_context()); + if (!credential) { + // For now, don't show a Touch ID prompt if no credential exists. + // TODO(martinkr): Prompt for the fingerprint anyway, once dispatch to this + // authenticator is moved behind user interaction with the authenticator + // selection UI. std::move(callback()) .Run(CtapDeviceResponseCode::kCtap2ErrNoCredentials, base::nullopt); return; @@ -129,7 +97,7 @@ void GetAssertionOperation::PromptTouchIdDone(bool success) { // Decrypt the user entity from the credential ID. base::Optional<CredentialMetadata::UserEntity> credential_user = CredentialMetadata::UnsealCredentialId(metadata_secret(), RpId(), - credential_id); + credential->credential_id); if (!credential_user) { // The keychain query already filtered for the RP ID encoded under this // operation's metadata secret, so the credential id really should have @@ -141,25 +109,27 @@ void GetAssertionOperation::PromptTouchIdDone(bool success) { } base::ScopedCFTypeRef<SecKeyRef> public_key( - Keychain::GetInstance().KeyCopyPublicKey(private_key)); + Keychain::GetInstance().KeyCopyPublicKey(credential->private_key)); if (!public_key) { DLOG(ERROR) << "failed to get public key for credential id " - << base::HexEncode(credential_id.data(), credential_id.size()); + << base::HexEncode(credential->credential_id.data(), + credential->credential_id.size()); std::move(callback()) .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt); return; } base::Optional<AuthenticatorData> authenticator_data = MakeAuthenticatorData( - RpId(), credential_id, SecKeyRefToECPublicKey(public_key)); + RpId(), credential->credential_id, SecKeyRefToECPublicKey(public_key)); if (!authenticator_data) { DLOG(ERROR) << "MakeAuthenticatorData failed"; std::move(callback()) .Run(CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt); return; } - base::Optional<std::vector<uint8_t>> signature = GenerateSignature( - *authenticator_data, request().client_data_hash(), private_key); + base::Optional<std::vector<uint8_t>> signature = + GenerateSignature(*authenticator_data, request().client_data_hash(), + credential->private_key); if (!signature) { DLOG(ERROR) << "GenerateSignature failed"; std::move(callback()) @@ -169,7 +139,7 @@ void GetAssertionOperation::PromptTouchIdDone(bool success) { auto response = AuthenticatorGetAssertionResponse( std::move(*authenticator_data), std::move(*signature)); response.SetCredential(PublicKeyCredentialDescriptor( - CredentialType::kPublicKey, std::move(credential_id))); + CredentialType::kPublicKey, std::move(credential->credential_id))); response.SetUserEntity(credential_user->ToPublicKeyCredentialUserEntity()); std::move(callback()) diff --git a/chromium/device/fido/mac/keychain.h b/chromium/device/fido/mac/keychain.h index 8772cea8997..d4e7558df5d 100644 --- a/chromium/device/fido/mac/keychain.h +++ b/chromium/device/fido/mac/keychain.h @@ -5,13 +5,20 @@ #ifndef DEVICE_FIDO_MAC_KEYCHAIN_H_ #define DEVICE_FIDO_MAC_KEYCHAIN_H_ +#include <stdint.h> +#include <set> +#include <string> +#include <vector> + #import <Foundation/Foundation.h> +#import <LocalAuthentication/LocalAuthentication.h> #import <Security/Security.h> #include "base/component_export.h" #include "base/mac/scoped_cftyperef.h" #include "base/macros.h" #include "base/no_destructor.h" +#include "base/optional.h" namespace device { namespace fido { @@ -25,37 +32,86 @@ namespace mac { // keychain-access-group entitlements, and therefore requires code signing with // a real Apple developer ID. We therefore group these function here, so they // can be mocked out in testing. -class COMPONENT_EXPORT(DEVICE_FIDO) API_AVAILABLE(macosx(10.12.2)) Keychain { +class COMPONENT_EXPORT(DEVICE_FIDO) API_AVAILABLE(macos(10.12.2)) Keychain { public: - static const Keychain& GetInstance(); + static Keychain& GetInstance(); // KeyCreateRandomKey wraps the |SecKeyCreateRandomKey| function. virtual base::ScopedCFTypeRef<SecKeyRef> KeyCreateRandomKey( CFDictionaryRef params, - CFErrorRef* error) const; + CFErrorRef* error); // KeyCreateSignature wraps the |SecKeyCreateSignature| function. virtual base::ScopedCFTypeRef<CFDataRef> KeyCreateSignature( SecKeyRef key, SecKeyAlgorithm algorithm, CFDataRef data, - CFErrorRef* error) const; + CFErrorRef* error); // KeyCopyPublicKey wraps the |SecKeyCopyPublicKey| function. - virtual base::ScopedCFTypeRef<SecKeyRef> KeyCopyPublicKey( - SecKeyRef key) const; + virtual base::ScopedCFTypeRef<SecKeyRef> KeyCopyPublicKey(SecKeyRef key); // ItemCopyMatching wraps the |SecItemCopyMatching| function. - virtual OSStatus ItemCopyMatching(CFDictionaryRef query, - CFTypeRef* result) const; + virtual OSStatus ItemCopyMatching(CFDictionaryRef query, CFTypeRef* result); // ItemDelete wraps the |SecItemDelete| function. - virtual OSStatus ItemDelete(CFDictionaryRef query) const; + virtual OSStatus ItemDelete(CFDictionaryRef query); + + protected: + Keychain(); + virtual ~Keychain(); private: friend class base::NoDestructor<Keychain>; - Keychain(); + friend class ScopedTouchIdTestEnvironment; + + // Set an override to the singleton instance returned by |GetInstance|. The + // caller keeps ownership of the injected keychain and must remove the + // override by calling |ClearInstanceOverride| before deleting it. + static void SetInstanceOverride(Keychain* keychain); + static void ClearInstanceOverride(); DISALLOW_COPY_AND_ASSIGN(Keychain); }; +// Credential represents a WebAuthn credential from the keychain. +struct COMPONENT_EXPORT(FIDO) Credential { + Credential(base::ScopedCFTypeRef<SecKeyRef> private_key, + std::vector<uint8_t> credential_id); + ~Credential(); + Credential(Credential&& other); + Credential& operator=(Credential&& other); + + // An opaque reference to the private key that can be used for signing. + base::ScopedCFTypeRef<SecKeyRef> private_key; + + // The credential ID is a handle to the key that gets passed to the RP. This + // ID is opaque to the RP, but is obtained by encrypting the credential + // metadata with a profile-specific metadata secret. See |CredentialMetadata| + // for more information. + std::vector<uint8_t> credential_id; + + private: + DISALLOW_COPY_AND_ASSIGN(Credential); +}; + +// Tries to find a credential for the given |rp_id| in the keychain. +// |credential_id_filter| may be used to select credentials by ID. If it is +// empty, any credential for the RP matches. If multiple credentials are found, +// only one is returned. +// +// An LAContext that has been successfully evaluated using |TouchIdContext| may +// be passed in |authenticaton_context|, in order to authorize the credential's +// private key for signing. The authentication may also be null if the caller +// only wants to check for existence of a key, but does not intend to create a +// signature from it. (I.e., the credential's SecKeyRef should not be passed to +// |KeyCreateSignature| if no authentication context was provided, since that +// would trigger a Touch ID prompt dialog). +COMPONENT_EXPORT(FIDO) +base::Optional<Credential> FindCredentialInKeychain( + const std::string& keychain_access_group, + const std::string& metadata_secret, + const std::string& rp_id, + const std::set<std::vector<uint8_t>>& credential_id_filter, + LAContext* authentication_context) API_AVAILABLE(macosx(10.12.2)); + } // namespace mac } // namespace fido } // namespace device diff --git a/chromium/device/fido/mac/keychain.mm b/chromium/device/fido/mac/keychain.mm index 692b7c8a776..d2e0fa795d3 100644 --- a/chromium/device/fido/mac/keychain.mm +++ b/chromium/device/fido/mac/keychain.mm @@ -4,21 +4,48 @@ #include "device/fido/mac/keychain.h" +#import <Foundation/Foundation.h> + +#include "base/mac/foundation_util.h" +#include "base/mac/mac_logging.h" +#include "base/stl_util.h" +#include "base/strings/sys_string_conversions.h" +#include "device/fido/mac/credential_metadata.h" + namespace device { namespace fido { namespace mac { +static API_AVAILABLE(macos(10.12.2)) Keychain* g_keychain_instance_override = + nullptr; + // static -const Keychain& Keychain::GetInstance() { - static const base::NoDestructor<Keychain> k; +Keychain& Keychain::GetInstance() { + if (g_keychain_instance_override) { + return *g_keychain_instance_override; + } + static base::NoDestructor<Keychain> k; return *k; } +// static +void Keychain::SetInstanceOverride(Keychain* keychain) { + CHECK(!g_keychain_instance_override); + g_keychain_instance_override = keychain; +} + +// static +void Keychain::ClearInstanceOverride() { + CHECK(g_keychain_instance_override); + g_keychain_instance_override = nullptr; +} + Keychain::Keychain() = default; +Keychain::~Keychain() = default; base::ScopedCFTypeRef<SecKeyRef> Keychain::KeyCreateRandomKey( CFDictionaryRef params, - CFErrorRef* error) const { + CFErrorRef* error) { return base::ScopedCFTypeRef<SecKeyRef>(SecKeyCreateRandomKey(params, error)); } @@ -26,25 +53,102 @@ base::ScopedCFTypeRef<CFDataRef> Keychain::KeyCreateSignature( SecKeyRef key, SecKeyAlgorithm algorithm, CFDataRef data, - CFErrorRef* error) const { + CFErrorRef* error) { return base::ScopedCFTypeRef<CFDataRef>( SecKeyCreateSignature(key, algorithm, data, error)); } -base::ScopedCFTypeRef<SecKeyRef> Keychain::KeyCopyPublicKey( - SecKeyRef key) const { +base::ScopedCFTypeRef<SecKeyRef> Keychain::KeyCopyPublicKey(SecKeyRef key) { return base::ScopedCFTypeRef<SecKeyRef>(SecKeyCopyPublicKey(key)); } -OSStatus Keychain::ItemCopyMatching(CFDictionaryRef query, - CFTypeRef* result) const { +OSStatus Keychain::ItemCopyMatching(CFDictionaryRef query, CFTypeRef* result) { return SecItemCopyMatching(query, result); } -OSStatus Keychain::ItemDelete(CFDictionaryRef query) const { +OSStatus Keychain::ItemDelete(CFDictionaryRef query) { return SecItemDelete(query); } +Credential::Credential(base::ScopedCFTypeRef<SecKeyRef> private_key_, + std::vector<uint8_t> credential_id_) + : private_key(std::move(private_key_)), + credential_id(std::move(credential_id_)) {} +Credential::~Credential() = default; +Credential::Credential(Credential&& other) = default; +Credential& Credential::operator=(Credential&& other) = default; + +base::Optional<Credential> FindCredentialInKeychain( + const std::string& keychain_access_group, + const std::string& metadata_secret, + const std::string& rp_id, + const std::set<std::vector<uint8_t>>& credential_id_filter, + LAContext* authentication_context) { + base::Optional<std::string> encoded_rp_id = + CredentialMetadata::EncodeRpId(metadata_secret, rp_id); + if (!encoded_rp_id) + return base::nullopt; + + base::ScopedCFTypeRef<CFMutableDictionaryRef> query( + CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr)); + CFDictionarySetValue(query, kSecClass, kSecClassKey); + CFDictionarySetValue(query, kSecAttrAccessGroup, + base::SysUTF8ToNSString(keychain_access_group)); + CFDictionarySetValue(query, kSecAttrLabel, + base::SysUTF8ToNSString(*encoded_rp_id)); + if (authentication_context) { + CFDictionarySetValue(query, kSecUseAuthenticationContext, + authentication_context); + } + CFDictionarySetValue(query, kSecReturnRef, @YES); + CFDictionarySetValue(query, kSecReturnAttributes, @YES); + CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitAll); + + base::ScopedCFTypeRef<CFArrayRef> keychain_items; + OSStatus status = Keychain::GetInstance().ItemCopyMatching( + query, reinterpret_cast<CFTypeRef*>(keychain_items.InitializeInto())); + if (status == errSecItemNotFound) { + // No credentials for the RP. + return base::nullopt; + } + if (status != errSecSuccess) { + OSSTATUS_DLOG(ERROR, status) << "SecItemCopyMatching failed"; + return base::nullopt; + } + + // Credentials for the RP exist. Find a match. + std::vector<uint8_t> credential_id; + base::ScopedCFTypeRef<SecKeyRef> private_key(nil); + for (CFIndex i = 0; i < CFArrayGetCount(keychain_items); ++i) { + CFDictionaryRef attributes = base::mac::CFCast<CFDictionaryRef>( + CFArrayGetValueAtIndex(keychain_items, i)); + CFDataRef application_label = base::mac::GetValueFromDictionary<CFDataRef>( + attributes, kSecAttrApplicationLabel); + SecKeyRef key = + base::mac::GetValueFromDictionary<SecKeyRef>(attributes, kSecValueRef); + if (!application_label || !key) { + // Corrupted keychain? + DLOG(ERROR) << "could not find application label or key ref: " + << attributes; + continue; + } + std::vector<uint8_t> cid(CFDataGetBytePtr(application_label), + CFDataGetBytePtr(application_label) + + CFDataGetLength(application_label)); + if (credential_id_filter.empty() || + base::ContainsKey(credential_id_filter, cid)) { + private_key.reset(key, base::scoped_policy::RETAIN); + credential_id = std::move(cid); + break; + } + } + if (private_key == nil) { + DVLOG(1) << "no allowed credential found"; + return base::nullopt; + } + return Credential(std::move(private_key), std::move(credential_id)); +} + } // namespace mac } // namespace fido } // namespace device diff --git a/chromium/device/fido/mac/make_credential_operation.mm b/chromium/device/fido/mac/make_credential_operation.mm index e5669be33cd..b83f45b6498 100644 --- a/chromium/device/fido/mac/make_credential_operation.mm +++ b/chromium/device/fido/mac/make_credential_operation.mm @@ -11,12 +11,15 @@ #include "base/mac/foundation_util.h" #include "base/mac/mac_logging.h" #include "base/mac/scoped_cftyperef.h" +#include "base/strings/utf_string_conversions.h" #include "device/fido/attestation_statement_formats.h" #include "device/fido/fido_constants.h" #include "device/fido/fido_parsing_utils.h" #include "device/fido/mac/credential_metadata.h" #include "device/fido/mac/keychain.h" #include "device/fido/mac/util.h" +#include "device/fido/strings/grit/fido_strings.h" +#include "ui/base/l10n/l10n_util.h" namespace device { namespace fido { @@ -65,9 +68,9 @@ void MakeCredentialOperation::Run() { return; } - // Prompt the user for consent. - // TODO(martinkr): Localize reason strings. - PromptTouchId("register with " + RpId()); + // Display the macOS Touch ID prompt. + PromptTouchId(l10n_util::GetStringFUTF16(IDS_WEBAUTHN_TOUCH_ID_PROMPT_REASON, + base::UTF8ToUTF16(RpId()))); } void MakeCredentialOperation::PromptTouchIdDone(bool success) { diff --git a/chromium/device/fido/mac/operation_base.h b/chromium/device/fido/mac/operation_base.h index 545252023a5..5dc476b64fa 100644 --- a/chromium/device/fido/mac/operation_base.h +++ b/chromium/device/fido/mac/operation_base.h @@ -37,7 +37,7 @@ class API_AVAILABLE(macosx(10.12.2)) OperationBase : public Operation { metadata_secret_(std::move(metadata_secret)), keychain_access_group_(std::move(keychain_access_group)), callback_(std::move(callback)), - touch_id_context_(std::make_unique<TouchIdContext>()) {} + touch_id_context_(TouchIdContext::Create()) {} ~OperationBase() override = default; @@ -56,13 +56,13 @@ class API_AVAILABLE(macosx(10.12.2)) OperationBase : public Operation { // PromptTouchId triggers a Touch ID consent dialog with the given reason // string. Subclasses implement the PromptTouchIdDone callback to receive the // result. - void PromptTouchId(std::string reason) { + void PromptTouchId(const base::string16& reason) { // The callback passed to TouchIdContext::Prompt will not fire if the // TouchIdContext itself has been deleted. Since that it is owned by this // class, there is no need to bind the callback to a weak ref here. touch_id_context_->PromptTouchId( - std::move(reason), base::BindOnce(&OperationBase::PromptTouchIdDone, - base::Unretained(this))); + reason, base::BindOnce(&OperationBase::PromptTouchIdDone, + base::Unretained(this))); } // Callback for |PromptTouchId|. @@ -96,7 +96,9 @@ class API_AVAILABLE(macosx(10.12.2)) OperationBase : public Operation { } const std::string& metadata_secret() const { return metadata_secret_; } - + const std::string& keychain_access_group() const { + return keychain_access_group_; + } const Request& request() const { return request_; } Callback& callback() { return callback_; } diff --git a/chromium/device/fido/mac/scoped_touch_id_test_environment.h b/chromium/device/fido/mac/scoped_touch_id_test_environment.h new file mode 100644 index 00000000000..8e6e112352b --- /dev/null +++ b/chromium/device/fido/mac/scoped_touch_id_test_environment.h @@ -0,0 +1,70 @@ +// Copyright 2018 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. + +#ifndef DEVICE_FIDO_MAC_SCOPED_TOUCH_ID_TEST_ENVIRONMENT_H_ +#define DEVICE_FIDO_MAC_SCOPED_TOUCH_ID_TEST_ENVIRONMENT_H_ + +#include <memory> + +#include "base/component_export.h" +#include "base/mac/availability.h" +#include "base/macros.h" + +namespace device { +namespace fido { +namespace mac { + +class FakeKeychain; +class FakeTouchIdContext; +class TouchIdContext; + +// ScopedTouchIdTestEnvironment overrides behavior of the Touch ID +// authenticator in testing. While in scope, it +// - installs a fake Keychain to avoid writing to the macOS keychain, which +// requires a valid code signature and keychain-access-group entitlement; +// - allows faking TouchIdContext instances returned by TouchIdContext to stub +// out Touch ID fingerprint prompts. +// Overrides are reset when the instance is destroyed. +class COMPONENT_EXPORT(DEVICE_FIDO) + API_AVAILABLE(macosx(10.12.2)) ScopedTouchIdTestEnvironment { + public: + ScopedTouchIdTestEnvironment(); + ~ScopedTouchIdTestEnvironment(); + + // ForgeNextTouchIdContext sets up the FakeTouchIdContext returned by the + // next call to TouchIdContext::Create. The fake will invoke the callback + // passed to TouchIdContext::PromptTouchId with the given result. + // + // It is a fatal error to call TouchIdContext::Create without invoking this + // method first while the test environment is in scope. + void ForgeNextTouchIdContext(bool simulate_prompt_success); + + // Sets the value returned by TouchIdContext::TouchIdAvailable. The default on + // instantiation of the test environment is true. + bool SetTouchIdAvailable(bool available); + + private: + static std::unique_ptr<TouchIdContext> ForwardCreate(); + static bool ForwardTouchIdAvailable(); + + std::unique_ptr<TouchIdContext> CreateTouchIdContext(); + bool TouchIdAvailable(); + + using CreateFuncPtr = decltype(&ForwardCreate); + CreateFuncPtr touch_id_context_create_ptr_; + using TouchIdAvailableFuncPtr = decltype(&ForwardTouchIdAvailable); + TouchIdAvailableFuncPtr touch_id_context_touch_id_available_ptr_; + + std::unique_ptr<FakeTouchIdContext> next_touch_id_context_; + std::unique_ptr<FakeKeychain> keychain_; + bool touch_id_available_ = true; + + DISALLOW_COPY_AND_ASSIGN(ScopedTouchIdTestEnvironment); +}; + +} // namespace mac +} // namespace fido +} // namespace device + +#endif // DEVICE_FIDO_MAC_SCOPED_TOUCH_ID_TEST_ENVIRONMENT_H_ diff --git a/chromium/device/fido/mac/scoped_touch_id_test_environment.mm b/chromium/device/fido/mac/scoped_touch_id_test_environment.mm new file mode 100644 index 00000000000..01aa6f25e1c --- /dev/null +++ b/chromium/device/fido/mac/scoped_touch_id_test_environment.mm @@ -0,0 +1,82 @@ +// Copyright 2018 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 "device/fido/mac/scoped_touch_id_test_environment.h" + +#import <Security/Security.h> + +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "device/fido/mac/fake_keychain.h" +#include "device/fido/mac/fake_touch_id_context.h" + +namespace device { +namespace fido { +namespace mac { + +static API_AVAILABLE(macosx(10.12.2)) + ScopedTouchIdTestEnvironment* g_current_environment = nullptr; + +ScopedTouchIdTestEnvironment::ScopedTouchIdTestEnvironment() + : keychain_(std::make_unique<FakeKeychain>()) { + DCHECK(!g_current_environment); + g_current_environment = this; + + // Override TouchIdContext::Create and TouchIdContext::IsAvailable. + touch_id_context_create_ptr_ = TouchIdContext::g_create_; + TouchIdContext::g_create_ = &ForwardCreate; + + touch_id_context_touch_id_available_ptr_ = + TouchIdContext::g_touch_id_available_; + TouchIdContext::g_touch_id_available_ = &ForwardTouchIdAvailable; + + Keychain::SetInstanceOverride(static_cast<Keychain*>(keychain_.get())); +} + +ScopedTouchIdTestEnvironment::~ScopedTouchIdTestEnvironment() { + DCHECK(touch_id_context_create_ptr_); + TouchIdContext::g_create_ = touch_id_context_create_ptr_; + + DCHECK(touch_id_context_touch_id_available_ptr_); + TouchIdContext::g_touch_id_available_ = + touch_id_context_touch_id_available_ptr_; + + Keychain::ClearInstanceOverride(); +} + +// static +std::unique_ptr<TouchIdContext> ScopedTouchIdTestEnvironment::ForwardCreate() { + return g_current_environment->CreateTouchIdContext(); +} + +// static +bool ScopedTouchIdTestEnvironment::ForwardTouchIdAvailable() { + return g_current_environment->TouchIdAvailable(); +} + +bool ScopedTouchIdTestEnvironment::SetTouchIdAvailable(bool available) { + return touch_id_available_ = available; +} + +bool ScopedTouchIdTestEnvironment::TouchIdAvailable() { + return touch_id_available_; +} + +void ScopedTouchIdTestEnvironment::ForgeNextTouchIdContext( + bool simulate_prompt_success) { + CHECK(!next_touch_id_context_); + next_touch_id_context_ = base::WrapUnique(new FakeTouchIdContext); + next_touch_id_context_->set_callback_result(simulate_prompt_success); +} + +std::unique_ptr<TouchIdContext> +ScopedTouchIdTestEnvironment::CreateTouchIdContext() { + CHECK(next_touch_id_context_) << "Call ForgeNextTouchIdContext() for every " + "context created in the test environment."; + return std::move(next_touch_id_context_); +} + +} // namespace mac +} // namespace fido +} // namespace device diff --git a/chromium/device/fido/mac/touch_id_context.h b/chromium/device/fido/mac/touch_id_context.h index e54f64d3d85..2eff4cad516 100644 --- a/chromium/device/fido/mac/touch_id_context.h +++ b/chromium/device/fido/mac/touch_id_context.h @@ -9,31 +9,48 @@ #import <Security/Security.h> #include "base/callback.h" +#include "base/component_export.h" #include "base/mac/scoped_cftyperef.h" #include "base/mac/scoped_nsobject.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" +#include "base/strings/string16.h" namespace device { namespace fido { namespace mac { // TouchIdContext wraps a macOS Touch ID consent prompt for signing with a -// secure enclave key. -class API_AVAILABLE(macosx(10.12.2)) TouchIdContext { +// secure enclave key. It is a essentially a simpler facade for the LAContext +// class from the macOS LocalAuthentication framework (c.f. +// https://developer.apple.com/documentation/localauthentication/lacontext?language=objc). +// +// Use |Create| to instantiate a new context. Multiple instances can be created +// at the same time. However, calling |PromptTouchId| on one instance will +// cancel any other pending evaluations with an error. Deleting an instance +// will invalidate any pending evaluation prompts (i.e. the dialog will +// disappear and evaluation will fail with an error). +class COMPONENT_EXPORT(DEVICE_FIDO) + API_AVAILABLE(macosx(10.12.2)) TouchIdContext { public: // The callback is invoked when the Touch ID prompt completes. It receives a // boolean indicating whether obtaining the fingerprint was successful. using Callback = base::OnceCallback<void(bool)>; - TouchIdContext(); - ~TouchIdContext(); + // Factory method for instantiating a TouchIdContext. + static std::unique_ptr<TouchIdContext> Create(); + + // Returns whether Touch ID is supported on the current hardware and set up to + // be used. + static bool TouchIdAvailable(); + + virtual ~TouchIdContext(); // PromptTouchId displays a Touch ID consent prompt with the provided reason // string to the user. On completion or error, the provided callback is // invoked, unless the TouchIdContext instance has been destroyed in the // meantime (in which case nothing happens). - void PromptTouchId(std::string reason, Callback callback); + virtual void PromptTouchId(const base::string16& reason, Callback callback); // authentication_context returns the LAContext used for the Touch ID prompt. LAContext* authentication_context() const { return context_; } @@ -42,7 +59,19 @@ class API_AVAILABLE(macosx(10.12.2)) TouchIdContext { // evaluated/authorized in the Touch ID prompt. SecAccessControlRef access_control() const { return access_control_; } + protected: + TouchIdContext(); + private: + using CreateFuncPtr = decltype(&Create); + using TouchIdAvailableFuncPtr = decltype(&TouchIdAvailable); + + static CreateFuncPtr g_create_; + static TouchIdAvailableFuncPtr g_touch_id_available_; + + static std::unique_ptr<TouchIdContext> CreateImpl(); + static bool TouchIdAvailableImpl(); + void RunCallback(bool success); base::scoped_nsobject<LAContext> context_; @@ -50,6 +79,7 @@ class API_AVAILABLE(macosx(10.12.2)) TouchIdContext { Callback callback_; base::WeakPtrFactory<TouchIdContext> weak_ptr_factory_; + friend class ScopedTouchIdTestEnvironment; DISALLOW_COPY_AND_ASSIGN(TouchIdContext); }; diff --git a/chromium/device/fido/mac/touch_id_context.mm b/chromium/device/fido/mac/touch_id_context.mm index d196ea59c6f..25cef5ff10d 100644 --- a/chromium/device/fido/mac/touch_id_context.mm +++ b/chromium/device/fido/mac/touch_id_context.mm @@ -9,6 +9,7 @@ #include "base/bind.h" #include "base/logging.h" #include "base/mac/foundation_util.h" +#include "base/memory/ptr_util.h" #include "base/sequenced_task_runner.h" #include "base/strings/sys_string_conversions.h" #include "base/threading/sequenced_task_runner_handle.h" @@ -20,14 +21,49 @@ namespace mac { namespace { API_AVAILABLE(macosx(10.12.2)) base::ScopedCFTypeRef<SecAccessControlRef> DefaultAccessControl() { + // The default access control policy used for WebAuthn credentials stored by + // the Touch ID platform authenticator. return base::ScopedCFTypeRef<SecAccessControlRef>( SecAccessControlCreateWithFlags( kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, - kSecAccessControlPrivateKeyUsage | kSecAccessControlTouchIDAny, + kSecAccessControlPrivateKeyUsage | kSecAccessControlUserPresence, nullptr)); } } // namespace +// static +std::unique_ptr<TouchIdContext> TouchIdContext::CreateImpl() { + return base::WrapUnique(new TouchIdContext()); +} + +// static +TouchIdContext::CreateFuncPtr TouchIdContext::g_create_ = + &TouchIdContext::CreateImpl; + +// static +std::unique_ptr<TouchIdContext> TouchIdContext::Create() { + // Testing seam to allow faking Touch ID in tests. + return (*g_create_)(); +} + +// static +bool TouchIdContext::TouchIdAvailableImpl() { + base::scoped_nsobject<LAContext> context([[LAContext alloc] init]); + return + [context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics + error:nil]; +} + +// static +TouchIdContext::TouchIdAvailableFuncPtr TouchIdContext::g_touch_id_available_ = + &TouchIdContext::TouchIdAvailableImpl; + +// static +bool TouchIdContext::TouchIdAvailable() { + // Testing seam to allow faking Touch ID in tests. + return (*g_touch_id_available_)(); +} + TouchIdContext::TouchIdContext() : context_([[LAContext alloc] init]), access_control_(DefaultAccessControl()), @@ -41,7 +77,8 @@ TouchIdContext::~TouchIdContext() { [context_ invalidate]; } -void TouchIdContext::PromptTouchId(std::string reason, Callback callback) { +void TouchIdContext::PromptTouchId(const base::string16& reason, + Callback callback) { callback_ = std::move(callback); scoped_refptr<base::SequencedTaskRunner> runner = base::SequencedTaskRunnerHandle::Get(); @@ -52,7 +89,7 @@ void TouchIdContext::PromptTouchId(std::string reason, Callback callback) { // the sign bit there. [context_ evaluateAccessControl:access_control_ operation:LAAccessControlOperationUseKeySign - localizedReason:base::SysUTF8ToNSString(reason) + localizedReason:base::SysUTF16ToNSString(reason) reply:^(BOOL success, NSError* error) { // The reply block is invoked in a separate // thread. We want to invoke the callback in the diff --git a/chromium/device/fido/mac/util.h b/chromium/device/fido/mac/util.h index 6f6d8caa3dc..0b4e56d5f5a 100644 --- a/chromium/device/fido/mac/util.h +++ b/chromium/device/fido/mac/util.h @@ -47,8 +47,6 @@ base::Optional<std::vector<uint8_t>> GenerateSignature( std::unique_ptr<ECPublicKey> SecKeyRefToECPublicKey(SecKeyRef public_key_ref) API_AVAILABLE(macosx(10.12.2)); -std::vector<uint8_t> TouchIdAaguid(); - } // namespace mac } // namespace fido } // namespace device diff --git a/chromium/device/fido/mac/util.mm b/chromium/device/fido/mac/util.mm index af4f61051a6..b91556f5643 100644 --- a/chromium/device/fido/mac/util.mm +++ b/chromium/device/fido/mac/util.mm @@ -31,14 +31,11 @@ using base::scoped_nsobject; using cbor::CBORWriter; using cbor::CBORValue; -// The authenticator AAGUID value. -constexpr std::array<uint8_t, 16> kAaguid = {0xad, 0xce, 0x00, 0x02, 0x35, 0xbc, - 0xc6, 0x0a, 0x64, 0x8b, 0x0b, 0x25, - 0xf1, 0xf0, 0x55, 0x03}; - -std::vector<uint8_t> TouchIdAaguid() { - return std::vector<uint8_t>(kAaguid.begin(), kAaguid.end()); -} +// WebAuthn requires an all-zero AAGUID for authenticators using +// self-attestation. +constexpr std::array<uint8_t, 16> kAaguid = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; // SecKeyRefToECPublicKey converts a SecKeyRef for a public key into an // equivalent |ECPublicKey| instance. It returns |nullptr| if the key cannot be diff --git a/chromium/device/fido/make_credential_handler_unittest.cc b/chromium/device/fido/make_credential_handler_unittest.cc index f5b277db77b..8b47f1b8f05 100644 --- a/chromium/device/fido/make_credential_handler_unittest.cc +++ b/chromium/device/fido/make_credential_handler_unittest.cc @@ -5,21 +5,28 @@ #include <memory> #include <utility> +#include "base/bind.h" +#include "base/bind_helpers.h" #include "base/test/scoped_feature_list.h" #include "base/test/scoped_task_environment.h" #include "device/base/features.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" +#include "device/bluetooth/test/mock_bluetooth_adapter.h" #include "device/fido/authenticator_make_credential_response.h" #include "device/fido/authenticator_selection_criteria.h" #include "device/fido/ctap_make_credential_request.h" +#include "device/fido/device_response_converter.h" #include "device/fido/fake_fido_discovery.h" #include "device/fido/fido_constants.h" #include "device/fido/fido_device.h" +#include "device/fido/fido_device_authenticator.h" #include "device/fido/fido_parsing_utils.h" #include "device/fido/fido_test_data.h" #include "device/fido/fido_transport_protocol.h" #include "device/fido/make_credential_request_handler.h" #include "device/fido/mock_fido_device.h" #include "device/fido/test_callback_receiver.h" +#include "device/fido/virtual_ctap2_device.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -29,16 +36,25 @@ namespace device { namespace { -using TestMakeCredentialRequestCallback = test::StatusAndValueCallbackReceiver< +using TestMakeCredentialRequestCallback = test::StatusAndValuesCallbackReceiver< FidoReturnCode, - base::Optional<AuthenticatorMakeCredentialResponse>>; + base::Optional<AuthenticatorMakeCredentialResponse>, + FidoTransportProtocol>; } // namespace class FidoMakeCredentialHandlerTest : public ::testing::Test { public: - void ForgeNextHidDiscovery() { + FidoMakeCredentialHandlerTest() { + mock_adapter_ = + base::MakeRefCounted<::testing::NiceMock<MockBluetoothAdapter>>(); + BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter_); + } + + void ForgeDiscoveries() { discovery_ = scoped_fake_discovery_factory_.ForgeNextHidDiscovery(); + ble_discovery_ = scoped_fake_discovery_factory_.ForgeNextBleDiscovery(); + nfc_discovery_ = scoped_fake_discovery_factory_.ForgeNextNfcDiscovery(); } std::unique_ptr<MakeCredentialRequestHandler> CreateMakeCredentialHandler() { @@ -49,7 +65,7 @@ class FidoMakeCredentialHandlerTest : public ::testing::Test { std::unique_ptr<MakeCredentialRequestHandler> CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria( AuthenticatorSelectionCriteria authenticator_selection_criteria) { - ForgeNextHidDiscovery(); + ForgeDiscoveries(); PublicKeyCredentialRpEntity rp(test_data::kRelyingPartyId); PublicKeyCredentialUserEntity user( fido_parsing_utils::Materialize(test_data::kUserId)); @@ -60,12 +76,38 @@ class FidoMakeCredentialHandlerTest : public ::testing::Test { test_data::kClientDataHash, std::move(rp), std::move(user), std::move(credential_params)); - return std::make_unique<MakeCredentialRequestHandler>( - nullptr, - base::flat_set<FidoTransportProtocol>( - {FidoTransportProtocol::kUsbHumanInterfaceDevice}), - std::move(request_parameter), + auto handler = std::make_unique<MakeCredentialRequestHandler>( + nullptr, supported_transports_, std::move(request_parameter), std::move(authenticator_selection_criteria), cb_.callback()); + handler->SetPlatformAuthenticatorOrMarkUnavailable( + CreatePlatformAuthenticator()); + return handler; + } + + void ExpectAllowedTransportsForRequestAre( + MakeCredentialRequestHandler* request_handler, + base::flat_set<FidoTransportProtocol> transports) { + using Transport = FidoTransportProtocol; + if (base::ContainsKey(transports, Transport::kUsbHumanInterfaceDevice)) + discovery()->WaitForCallToStartAndSimulateSuccess(); + if (base::ContainsKey(transports, Transport::kBluetoothLowEnergy)) + ble_discovery()->WaitForCallToStartAndSimulateSuccess(); + if (base::ContainsKey(transports, Transport::kNearFieldCommunication)) + nfc_discovery()->WaitForCallToStartAndSimulateSuccess(); + + scoped_task_environment_.FastForwardUntilNoTasksRemain(); + EXPECT_FALSE(callback().was_called()); + + if (!base::ContainsKey(transports, Transport::kUsbHumanInterfaceDevice)) + EXPECT_FALSE(discovery()->is_start_requested()); + if (!base::ContainsKey(transports, Transport::kBluetoothLowEnergy)) + EXPECT_FALSE(ble_discovery()->is_start_requested()); + if (!base::ContainsKey(transports, Transport::kNearFieldCommunication)) + EXPECT_FALSE(nfc_discovery()->is_start_requested()); + + EXPECT_THAT( + request_handler->transport_availability_info().available_transports, + ::testing::UnorderedElementsAreArray(transports)); } void InitFeatureListAndDisableCtapFlag() { @@ -73,31 +115,61 @@ class FidoMakeCredentialHandlerTest : public ::testing::Test { } test::FakeFidoDiscovery* discovery() const { return discovery_; } + test::FakeFidoDiscovery* ble_discovery() const { return ble_discovery_; } + test::FakeFidoDiscovery* nfc_discovery() const { return nfc_discovery_; } TestMakeCredentialRequestCallback& callback() { return cb_; } + void set_mock_platform_device(std::unique_ptr<MockFidoDevice> device) { + mock_platform_device_ = std::move(device); + } + + void set_supported_transports( + base::flat_set<FidoTransportProtocol> transports) { + supported_transports_ = std::move(transports); + } + protected: + base::Optional<PlatformAuthenticatorInfo> CreatePlatformAuthenticator() { + if (!mock_platform_device_) + return base::nullopt; + return PlatformAuthenticatorInfo( + std::make_unique<FidoDeviceAuthenticator>(mock_platform_device_.get()), + false /* has_recognized_mac_touch_id_credential_available */); + } + base::test::ScopedFeatureList scoped_feature_list_; base::test::ScopedTaskEnvironment scoped_task_environment_{ base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME}; test::ScopedFakeFidoDiscoveryFactory scoped_fake_discovery_factory_; test::FakeFidoDiscovery* discovery_; + test::FakeFidoDiscovery* ble_discovery_; + test::FakeFidoDiscovery* nfc_discovery_; + scoped_refptr<::testing::NiceMock<MockBluetoothAdapter>> mock_adapter_; + std::unique_ptr<MockFidoDevice> mock_platform_device_; TestMakeCredentialRequestCallback cb_; + base::flat_set<FidoTransportProtocol> supported_transports_ = + GetAllTransportProtocols(); }; +TEST_F(FidoMakeCredentialHandlerTest, TransportAvailabilityInfo) { + auto request_handler = CreateMakeCredentialHandler(); + + EXPECT_EQ(FidoRequestHandlerBase::RequestType::kMakeCredential, + request_handler->transport_availability_info().request_type); + EXPECT_EQ(test_data::kRelyingPartyId, + request_handler->transport_availability_info().rp_id); +} + TEST_F(FidoMakeCredentialHandlerTest, TestCtap2MakeCredentialWithFlagEnabled) { auto request_handler = CreateMakeCredentialHandler(); discovery()->WaitForCallToStartAndSimulateSuccess(); - auto device = std::make_unique<MockFidoDevice>(); - EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0")); - device->ExpectCtap2CommandAndRespondWith( - CtapRequestCommand::kAuthenticatorGetInfo, - test_data::kTestAuthenticatorGetInfoResponse); + auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(); device->ExpectCtap2CommandAndRespondWith( CtapRequestCommand::kAuthenticatorMakeCredential, test_data::kTestMakeCredentialResponse); - discovery()->AddDevice(std::move(device)); + callback().WaitForCallback(); EXPECT_EQ(FidoReturnCode::kSuccess, callback().status()); EXPECT_TRUE(request_handler->is_complete()); @@ -108,15 +180,12 @@ TEST_F(FidoMakeCredentialHandlerTest, TestU2fRegisterWithFlagEnabled) { auto request_handler = CreateMakeCredentialHandler(); discovery()->WaitForCallToStartAndSimulateSuccess(); - auto device = std::make_unique<MockFidoDevice>(); - EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0")); - device->ExpectCtap2CommandAndRespondWith( - CtapRequestCommand::kAuthenticatorGetInfo, base::nullopt); + auto device = MockFidoDevice::MakeU2fWithGetInfoExpectation(); device->ExpectRequestAndRespondWith( test_data::kU2fRegisterCommandApdu, test_data::kApduEncodedNoErrorRegisterResponse); - discovery()->AddDevice(std::move(device)); + callback().WaitForCallback(); EXPECT_EQ(FidoReturnCode::kSuccess, callback().status()); EXPECT_TRUE(request_handler->is_complete()); @@ -150,83 +219,103 @@ TEST_F(FidoMakeCredentialHandlerTest, U2fRegisterWithUserVerificationRequired) { UserVerificationRequirement::kRequired)); discovery()->WaitForCallToStartAndSimulateSuccess(); - auto device = std::make_unique<MockFidoDevice>(); - EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0")); - device->ExpectCtap2CommandAndRespondWith( - CtapRequestCommand::kAuthenticatorGetInfo, base::nullopt); - + auto device = MockFidoDevice::MakeU2fWithGetInfoExpectation(); discovery()->AddDevice(std::move(device)); scoped_task_environment_.FastForwardUntilNoTasksRemain(); EXPECT_FALSE(callback().was_called()); } -TEST_F(FidoMakeCredentialHandlerTest, - U2fRegisterWithPlatformDeviceRequirement) { +TEST_F(FidoMakeCredentialHandlerTest, U2fRegisterWithResidentKeyRequirement) { auto request_handler = CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria( AuthenticatorSelectionCriteria( - AuthenticatorSelectionCriteria::AuthenticatorAttachment:: - kPlatform, - false /* require_resident_key */, + AuthenticatorSelectionCriteria::AuthenticatorAttachment::kAny, + true /* require_resident_key */, UserVerificationRequirement::kPreferred)); discovery()->WaitForCallToStartAndSimulateSuccess(); - auto device = std::make_unique<MockFidoDevice>(); - EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0")); - device->ExpectCtap2CommandAndRespondWith( - CtapRequestCommand::kAuthenticatorGetInfo, base::nullopt); - + auto device = MockFidoDevice::MakeU2fWithGetInfoExpectation(); discovery()->AddDevice(std::move(device)); scoped_task_environment_.FastForwardUntilNoTasksRemain(); EXPECT_FALSE(callback().was_called()); } -TEST_F(FidoMakeCredentialHandlerTest, U2fRegisterWithResidentKeyRequirement) { +TEST_F(FidoMakeCredentialHandlerTest, UserVerificationRequirementNotMet) { auto request_handler = CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria( AuthenticatorSelectionCriteria( AuthenticatorSelectionCriteria::AuthenticatorAttachment::kAny, - true /* require_resident_key */, - UserVerificationRequirement::kPreferred)); + false /* require_resident_key */, + UserVerificationRequirement::kRequired)); discovery()->WaitForCallToStartAndSimulateSuccess(); - auto device = std::make_unique<MockFidoDevice>(); - EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0")); - device->ExpectCtap2CommandAndRespondWith( - CtapRequestCommand::kAuthenticatorGetInfo, base::nullopt); - + auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation( + test_data::kTestGetInfoResponseWithoutUvSupport); discovery()->AddDevice(std::move(device)); scoped_task_environment_.FastForwardUntilNoTasksRemain(); EXPECT_FALSE(callback().was_called()); } -TEST_F(FidoMakeCredentialHandlerTest, - UserVerificationAuthenticatorSelectionCriteria) { +// TODO(crbug.com/873710): Platform authenticators are temporarily disabled if +// AuthenticatorAttachment is unset (kAny). +TEST_F(FidoMakeCredentialHandlerTest, AnyAttachment) { + auto platform_device = MockFidoDevice::MakeCtap( + ReadCTAPGetInfoResponse(test_data::kTestGetInfoResponsePlatformDevice)); + platform_device->SetDeviceTransport(FidoTransportProtocol::kInternal); + set_mock_platform_device(std::move(platform_device)); + EXPECT_CALL(*mock_adapter_, IsPresent()).WillOnce(::testing::Return(true)); auto request_handler = CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria( AuthenticatorSelectionCriteria( AuthenticatorSelectionCriteria::AuthenticatorAttachment::kAny, false /* require_resident_key */, - UserVerificationRequirement::kRequired)); - discovery()->WaitForCallToStartAndSimulateSuccess(); - - auto device = std::make_unique<MockFidoDevice>(); - EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0")); - device->ExpectCtap2CommandAndRespondWith( - CtapRequestCommand::kAuthenticatorGetInfo, - test_data::kTestGetInfoResponseWithoutUvSupport); - - discovery()->AddDevice(std::move(device)); + UserVerificationRequirement::kPreferred)); + // MakeCredentialHandler will not dispatch the kAny request to the platform + // authenticator since the request does not get dispatched through UI. Despite + // setting a platform authenticator, the internal transport never becomes + // available. scoped_task_environment_.FastForwardUntilNoTasksRemain(); EXPECT_FALSE(callback().was_called()); + + // kCloudAssistedBluetoothLowEnergy not yet supported for MakeCredential. + ExpectAllowedTransportsForRequestAre( + request_handler.get(), {FidoTransportProtocol::kBluetoothLowEnergy, + FidoTransportProtocol::kNearFieldCommunication, + FidoTransportProtocol::kUsbHumanInterfaceDevice}); } -TEST_F(FidoMakeCredentialHandlerTest, - PlatformDeviceAuthenticatorSelectionCriteria) { +TEST_F(FidoMakeCredentialHandlerTest, CrossPlatformAttachment) { + EXPECT_CALL(*mock_adapter_, IsPresent()).WillOnce(::testing::Return(true)); + auto request_handler = + CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria( + AuthenticatorSelectionCriteria( + AuthenticatorSelectionCriteria::AuthenticatorAttachment:: + kCrossPlatform, + false /* require_resident_key */, + UserVerificationRequirement::kPreferred)); + + // kCloudAssistedBluetoothLowEnergy not yet supported for MakeCredential. + ExpectAllowedTransportsForRequestAre( + request_handler.get(), {FidoTransportProtocol::kBluetoothLowEnergy, + FidoTransportProtocol::kNearFieldCommunication, + FidoTransportProtocol::kUsbHumanInterfaceDevice}); +} + +TEST_F(FidoMakeCredentialHandlerTest, PlatformAttachment) { + // Add a platform device to trigger the transport actually becoming available. + // We don't care about the result of the request. + auto platform_device = MockFidoDevice::MakeCtapWithGetInfoExpectation( + test_data::kTestGetInfoResponsePlatformDevice); + platform_device->SetDeviceTransport(FidoTransportProtocol::kInternal); + platform_device->ExpectCtap2CommandAndDoNotRespond( + CtapRequestCommand::kAuthenticatorMakeCredential); + EXPECT_CALL(*platform_device, Cancel()); + set_mock_platform_device(std::move(platform_device)); + auto request_handler = CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria( AuthenticatorSelectionCriteria( @@ -234,14 +323,22 @@ TEST_F(FidoMakeCredentialHandlerTest, kPlatform, false /* require_resident_key */, UserVerificationRequirement::kRequired)); - discovery()->WaitForCallToStartAndSimulateSuccess(); - auto device = std::make_unique<MockFidoDevice>(); - EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0")); - device->ExpectCtap2CommandAndRespondWith( - CtapRequestCommand::kAuthenticatorGetInfo, - test_data::kTestGetInfoResponseCrossPlatformDevice); + ExpectAllowedTransportsForRequestAre(request_handler.get(), + {FidoTransportProtocol::kInternal}); +} + +TEST_F(FidoMakeCredentialHandlerTest, ResidentKeyRequirementNotMet) { + auto request_handler = + CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria( + AuthenticatorSelectionCriteria( + AuthenticatorSelectionCriteria::AuthenticatorAttachment::kAny, + true /* require_resident_key */, + UserVerificationRequirement::kPreferred)); + discovery()->WaitForCallToStartAndSimulateSuccess(); + auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation( + test_data::kTestGetInfoResponseWithoutResidentKeySupport); discovery()->AddDevice(std::move(device)); scoped_task_environment_.FastForwardUntilNoTasksRemain(); @@ -249,29 +346,99 @@ TEST_F(FidoMakeCredentialHandlerTest, } TEST_F(FidoMakeCredentialHandlerTest, - ResidentKeyAuthenticatorSelectionCriteria) { + AuthenticatorSelectionCriteriaSatisfiedByCrossPlatformDevice) { + set_supported_transports({FidoTransportProtocol::kUsbHumanInterfaceDevice}); auto request_handler = CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria( AuthenticatorSelectionCriteria( - AuthenticatorSelectionCriteria::AuthenticatorAttachment::kAny, + AuthenticatorSelectionCriteria::AuthenticatorAttachment:: + kCrossPlatform, true /* require_resident_key */, - UserVerificationRequirement::kPreferred)); + UserVerificationRequirement::kRequired)); discovery()->WaitForCallToStartAndSimulateSuccess(); - auto device = std::make_unique<MockFidoDevice>(); - EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0")); + auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(); device->ExpectCtap2CommandAndRespondWith( + CtapRequestCommand::kAuthenticatorMakeCredential, + test_data::kTestMakeCredentialResponse); + discovery()->AddDevice(std::move(device)); + + callback().WaitForCallback(); + EXPECT_EQ(FidoReturnCode::kSuccess, callback().status()); + + EXPECT_THAT( + request_handler->transport_availability_info().available_transports, + ::testing::UnorderedElementsAre( + FidoTransportProtocol::kUsbHumanInterfaceDevice)); +} + +TEST_F(FidoMakeCredentialHandlerTest, + AuthenticatorSelectionCriteriaSatisfiedByPlatformDevice) { + set_supported_transports({FidoTransportProtocol::kInternal}); + auto platform_device = MockFidoDevice::MakeCtap( + ReadCTAPGetInfoResponse(test_data::kTestGetInfoResponsePlatformDevice)); + platform_device->SetDeviceTransport(FidoTransportProtocol::kInternal); + EXPECT_CALL(*platform_device, GetId()) + .WillRepeatedly(testing::Return("device0")); + platform_device->ExpectCtap2CommandAndRespondWith( CtapRequestCommand::kAuthenticatorGetInfo, - test_data::kTestGetInfoResponseWithoutResidentKeySupport); + test_data::kTestGetInfoResponsePlatformDevice); + platform_device->ExpectCtap2CommandAndRespondWith( + CtapRequestCommand::kAuthenticatorMakeCredential, + test_data::kTestMakeCredentialResponse); + set_mock_platform_device(std::move(platform_device)); + auto request_handler = + CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria( + AuthenticatorSelectionCriteria( + AuthenticatorSelectionCriteria::AuthenticatorAttachment:: + kPlatform, + true /* require_resident_key */, + UserVerificationRequirement::kRequired)); + + callback().WaitForCallback(); + EXPECT_EQ(FidoReturnCode::kSuccess, callback().status()); + + EXPECT_THAT( + request_handler->transport_availability_info().available_transports, + ::testing::UnorderedElementsAre(FidoTransportProtocol::kInternal)); +} + +// A cross-platform authenticator claiming to be a platform authenticator as per +// its GetInfo response is rejected. +TEST_F(FidoMakeCredentialHandlerTest, + CrossPlatformAuthenticatorPretendingToBePlatform) { + auto request_handler = + CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria( + AuthenticatorSelectionCriteria( + AuthenticatorSelectionCriteria::AuthenticatorAttachment:: + kCrossPlatform, + false /* require_resident_key */, + UserVerificationRequirement::kPreferred)); + discovery()->WaitForCallToStartAndSimulateSuccess(); + + auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation( + test_data::kTestGetInfoResponsePlatformDevice); discovery()->AddDevice(std::move(device)); scoped_task_environment_.FastForwardUntilNoTasksRemain(); EXPECT_FALSE(callback().was_called()); } +// A platform authenticator claiming to be a cross-platform authenticator as per +// its GetInfo response is rejected. TEST_F(FidoMakeCredentialHandlerTest, - SatisfyAllAuthenticatorSelectionCriteria) { + PlatformAuthenticatorPretendingToBeCrossPlatform) { + auto platform_device = MockFidoDevice::MakeCtap( + ReadCTAPGetInfoResponse(test_data::kTestAuthenticatorGetInfoResponse)); + platform_device->SetDeviceTransport(FidoTransportProtocol::kInternal); + EXPECT_CALL(*platform_device, GetId()) + .WillRepeatedly(testing::Return("device0")); + platform_device->ExpectCtap2CommandAndRespondWith( + CtapRequestCommand::kAuthenticatorGetInfo, + test_data::kTestAuthenticatorGetInfoResponse); + set_mock_platform_device(std::move(platform_device)); + auto request_handler = CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria( AuthenticatorSelectionCriteria( @@ -279,17 +446,67 @@ TEST_F(FidoMakeCredentialHandlerTest, kPlatform, true /* require_resident_key */, UserVerificationRequirement::kRequired)); + + scoped_task_environment_.FastForwardUntilNoTasksRemain(); + EXPECT_FALSE(callback().was_called()); +} + +TEST_F(FidoMakeCredentialHandlerTest, SupportedTransportsAreOnlyBleAndNfc) { + const base::flat_set<FidoTransportProtocol> kBleAndNfc = { + FidoTransportProtocol::kBluetoothLowEnergy, + FidoTransportProtocol::kNearFieldCommunication, + }; + + set_supported_transports(kBleAndNfc); + EXPECT_CALL(*mock_adapter_, IsPresent()).WillOnce(::testing::Return(true)); + auto request_handler = + CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria( + AuthenticatorSelectionCriteria( + AuthenticatorSelectionCriteria::AuthenticatorAttachment:: + kCrossPlatform, + false /* require_resident_key */, + UserVerificationRequirement::kPreferred)); + + ExpectAllowedTransportsForRequestAre(request_handler.get(), kBleAndNfc); +} + +TEST_F(FidoMakeCredentialHandlerTest, IncorrectRpIdHash) { + auto request_handler = + CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria( + AuthenticatorSelectionCriteria( + AuthenticatorSelectionCriteria::AuthenticatorAttachment::kAny, + false /* require_resident_key */, + UserVerificationRequirement::kPreferred)); discovery()->WaitForCallToStartAndSimulateSuccess(); - auto device = std::make_unique<MockFidoDevice>(); - EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0")); - device->ExpectCtap2CommandAndRespondWith( - CtapRequestCommand::kAuthenticatorGetInfo, - test_data::kTestAuthenticatorGetInfoResponse); + auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(); device->ExpectCtap2CommandAndRespondWith( CtapRequestCommand::kAuthenticatorMakeCredential, - test_data::kTestMakeCredentialResponse); + test_data::kTestMakeCredentialResponseWithIncorrectRpIdHash); + discovery()->AddDevice(std::move(device)); + + scoped_task_environment_.FastForwardUntilNoTasksRemain(); + EXPECT_FALSE(callback().was_called()); +} + +// Tests that only authenticators with resident key support will successfully +// process MakeCredential request when the relying party requires using resident +// keys in AuthenicatorSelectionCriteria. +TEST_F(FidoMakeCredentialHandlerTest, + SuccessfulMakeCredentialWithResidentKeyOption) { + auto device = std::make_unique<VirtualCtap2Device>(); + AuthenticatorSupportedOptions option; + option.SetSupportsResidentKey(true); + device->SetAuthenticatorSupportedOptions(std::move(option)); + + auto request_handler = + CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria( + AuthenticatorSelectionCriteria( + AuthenticatorSelectionCriteria::AuthenticatorAttachment::kAny, + true /* require_resident_key */, + UserVerificationRequirement::kPreferred)); + discovery()->WaitForCallToStartAndSimulateSuccess(); discovery()->AddDevice(std::move(device)); scoped_task_environment_.FastForwardUntilNoTasksRemain(); @@ -297,25 +514,73 @@ TEST_F(FidoMakeCredentialHandlerTest, EXPECT_EQ(FidoReturnCode::kSuccess, callback().status()); } -TEST_F(FidoMakeCredentialHandlerTest, IncompatibleUserVerificationSetting) { +// Tests that MakeCredential request fails when asking to use resident keys with +// authenticators that do not support resident key. +TEST_F(FidoMakeCredentialHandlerTest, + MakeCredentialFailsForIncompatibleResidentKeyOption) { + auto device = std::make_unique<VirtualCtap2Device>(); auto request_handler = CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria( AuthenticatorSelectionCriteria( AuthenticatorSelectionCriteria::AuthenticatorAttachment::kAny, - false /* require_resident_key */, - UserVerificationRequirement::kRequired)); + true /* require_resident_key */, + UserVerificationRequirement::kPreferred)); + discovery()->WaitForCallToStartAndSimulateSuccess(); + discovery()->AddDevice(std::move(device)); - auto device = std::make_unique<MockFidoDevice>(); - EXPECT_CALL(*device, GetId()).WillRepeatedly(testing::Return("device0")); - device->ExpectCtap2CommandAndRespondWith( - CtapRequestCommand::kAuthenticatorGetInfo, - test_data::kTestGetInfoResponseWithoutUvSupport); + scoped_task_environment_.FastForwardUntilNoTasksRemain(); + EXPECT_FALSE(callback().was_called()); +} + +// If a device with transport type kInternal returns a +// CTAP2_ERR_OPERATION_DENIED error, the request should complete with +// FidoReturnCode::kUserConsentDenied. +TEST_F(FidoMakeCredentialHandlerTest, + TestRequestWithOperationDeniedErrorPlatform) { + auto platform_device = MockFidoDevice::MakeCtapWithGetInfoExpectation( + test_data::kTestGetInfoResponsePlatformDevice); + platform_device->SetDeviceTransport(FidoTransportProtocol::kInternal); + platform_device->ExpectCtap2CommandAndRespondWithError( + CtapRequestCommand::kAuthenticatorMakeCredential, + CtapDeviceResponseCode::kCtap2ErrOperationDenied); + set_mock_platform_device(std::move(platform_device)); + + auto request_handler = + CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria( + AuthenticatorSelectionCriteria( + AuthenticatorSelectionCriteria::AuthenticatorAttachment:: + kPlatform, + false /* require_resident_key */, + UserVerificationRequirement::kPreferred)); + scoped_task_environment_.FastForwardUntilNoTasksRemain(); + EXPECT_TRUE(callback().was_called()); + EXPECT_EQ(FidoReturnCode::kUserConsentDenied, callback().status()); +} + +// Like |TestRequestWithOperationDeniedErrorPlatform|, but with a +// cross-platform device. +TEST_F(FidoMakeCredentialHandlerTest, + TestRequestWithOperationDeniedErrorCrossPlatform) { + auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(); + device->ExpectCtap2CommandAndRespondWithError( + CtapRequestCommand::kAuthenticatorMakeCredential, + CtapDeviceResponseCode::kCtap2ErrOperationDenied); + + auto request_handler = + CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria( + AuthenticatorSelectionCriteria( + AuthenticatorSelectionCriteria::AuthenticatorAttachment::kAny, + false /* require_resident_key */, + UserVerificationRequirement::kPreferred)); + + discovery()->WaitForCallToStartAndSimulateSuccess(); discovery()->AddDevice(std::move(device)); scoped_task_environment_.FastForwardUntilNoTasksRemain(); - EXPECT_FALSE(callback().was_called()); + EXPECT_TRUE(callback().was_called()); + EXPECT_EQ(FidoReturnCode::kUserConsentDenied, callback().status()); } } // namespace device diff --git a/chromium/device/fido/make_credential_request_handler.cc b/chromium/device/fido/make_credential_request_handler.cc index 804bb75f5a3..ef72a287562 100644 --- a/chromium/device/fido/make_credential_request_handler.cc +++ b/chromium/device/fido/make_credential_request_handler.cc @@ -4,49 +4,20 @@ #include "device/fido/make_credential_request_handler.h" +#include <set> #include <utility> #include "base/bind.h" +#include "base/stl_util.h" #include "device/fido/authenticator_make_credential_response.h" #include "device/fido/fido_authenticator.h" +#include "device/fido/fido_parsing_utils.h" +#include "device/fido/fido_transport_protocol.h" #include "device/fido/make_credential_task.h" #include "services/service_manager/public/cpp/connector.h" namespace device { -MakeCredentialRequestHandler::MakeCredentialRequestHandler( - service_manager::Connector* connector, - const base::flat_set<FidoTransportProtocol>& protocols, - CtapMakeCredentialRequest request, - AuthenticatorSelectionCriteria authenticator_selection_criteria, - RegisterResponseCallback completion_callback) - : MakeCredentialRequestHandler(connector, - protocols, - std::move(request), - authenticator_selection_criteria, - std::move(completion_callback), - AddPlatformAuthenticatorCallback()) {} - -MakeCredentialRequestHandler::MakeCredentialRequestHandler( - service_manager::Connector* connector, - const base::flat_set<FidoTransportProtocol>& protocols, - CtapMakeCredentialRequest request, - AuthenticatorSelectionCriteria authenticator_selection_criteria, - RegisterResponseCallback completion_callback, - AddPlatformAuthenticatorCallback add_platform_authenticator) - : FidoRequestHandler(connector, - protocols, - std::move(completion_callback), - std::move(add_platform_authenticator)), - request_parameter_(std::move(request)), - authenticator_selection_criteria_( - std::move(authenticator_selection_criteria)), - weak_factory_(this) { - Start(); -} - -MakeCredentialRequestHandler::~MakeCredentialRequestHandler() = default; - namespace { bool CheckIfAuthenticatorSelectionCriteriaAreSatisfied( @@ -72,6 +43,8 @@ bool CheckIfAuthenticatorSelectionCriteriaAreSatisfied( !options.supports_resident_key()) { return false; } + request->SetResidentKeySupported( + authenticator_selection_criteria.require_resident_key()); const auto& user_verification_requirement = authenticator_selection_criteria.user_verification_requirement(); @@ -85,8 +58,58 @@ bool CheckIfAuthenticatorSelectionCriteriaAreSatisfied( UvAvailability::kSupportedAndConfigured; } +base::flat_set<FidoTransportProtocol> GetTransportsAllowedByRP( + const AuthenticatorSelectionCriteria& authenticator_selection_criteria) { + using AttachmentType = + AuthenticatorSelectionCriteria::AuthenticatorAttachment; + const auto attachment_type = + authenticator_selection_criteria.authenticator_attachement(); + switch (attachment_type) { + case AttachmentType::kPlatform: + return {FidoTransportProtocol::kInternal}; + case AttachmentType::kCrossPlatform: + // Cloud-assisted BLE is not yet supported for MakeCredential requests. + return {FidoTransportProtocol::kUsbHumanInterfaceDevice, + FidoTransportProtocol::kBluetoothLowEnergy, + FidoTransportProtocol::kNearFieldCommunication}; + case AttachmentType::kAny: + // Cloud-assisted BLE is not yet supported for MakeCredential requests. + return {FidoTransportProtocol::kInternal, + FidoTransportProtocol::kNearFieldCommunication, + FidoTransportProtocol::kUsbHumanInterfaceDevice, + FidoTransportProtocol::kBluetoothLowEnergy}; + } + + NOTREACHED(); + return base::flat_set<FidoTransportProtocol>(); +} + } // namespace +MakeCredentialRequestHandler::MakeCredentialRequestHandler( + service_manager::Connector* connector, + const base::flat_set<FidoTransportProtocol>& supported_transports, + CtapMakeCredentialRequest request, + AuthenticatorSelectionCriteria authenticator_selection_criteria, + RegisterResponseCallback completion_callback) + : FidoRequestHandler( + connector, + base::STLSetIntersection<base::flat_set<FidoTransportProtocol>>( + supported_transports, + GetTransportsAllowedByRP(authenticator_selection_criteria)), + std::move(completion_callback)), + request_parameter_(std::move(request)), + authenticator_selection_criteria_( + std::move(authenticator_selection_criteria)), + weak_factory_(this) { + transport_availability_info().rp_id = request_parameter_.rp().rp_id(); + transport_availability_info().request_type = + FidoRequestHandlerBase::RequestType::kMakeCredential; + Start(); +} + +MakeCredentialRequestHandler::~MakeCredentialRequestHandler() = default; + void MakeCredentialRequestHandler::DispatchRequest( FidoAuthenticator* authenticator) { // The user verification field of the request may be adjusted to the @@ -99,8 +122,50 @@ void MakeCredentialRequestHandler::DispatchRequest( authenticator->MakeCredential( std::move(request_copy), - base::BindOnce(&MakeCredentialRequestHandler::OnAuthenticatorResponse, + base::BindOnce(&MakeCredentialRequestHandler::HandleResponse, weak_factory_.GetWeakPtr(), authenticator)); } +void MakeCredentialRequestHandler::HandleResponse( + FidoAuthenticator* authenticator, + CtapDeviceResponseCode response_code, + base::Optional<AuthenticatorMakeCredentialResponse> response) { + if (response_code != CtapDeviceResponseCode::kSuccess) { + OnAuthenticatorResponse(authenticator, response_code, base::nullopt); + return; + } + + const auto rp_id_hash = + fido_parsing_utils::CreateSHA256Hash(request_parameter_.rp().rp_id()); + + if (!response || response->GetRpIdHash() != rp_id_hash) { + OnAuthenticatorResponse( + authenticator, CtapDeviceResponseCode::kCtap2ErrOther, base::nullopt); + return; + } + + OnAuthenticatorResponse(authenticator, response_code, std::move(response)); +} + +void MakeCredentialRequestHandler::SetPlatformAuthenticatorOrMarkUnavailable( + base::Optional<PlatformAuthenticatorInfo> platform_authenticator_info) { + if (platform_authenticator_info) { + // TODO(crbug.com/873710): In the case of a request with + // AuthenticatorAttachment::kAny and when there is no embedder-provided + // transport selection UI, disable the platform authenticator to avoid the + // Touch ID fingerprint prompt competing with external devices. + const bool has_transport_selection_ui = + observer() && observer()->EmbedderControlsAuthenticatorDispatch( + *platform_authenticator_info->authenticator); + if (authenticator_selection_criteria_.authenticator_attachement() == + AuthenticatorSelectionCriteria::AuthenticatorAttachment::kAny && + !has_transport_selection_ui) { + platform_authenticator_info = base::nullopt; + } + } + + FidoRequestHandlerBase::SetPlatformAuthenticatorOrMarkUnavailable( + std::move(platform_authenticator_info)); +} + } // namespace device diff --git a/chromium/device/fido/make_credential_request_handler.h b/chromium/device/fido/make_credential_request_handler.h index 6edfdc0f926..a5e799f0f7f 100644 --- a/chromium/device/fido/make_credential_request_handler.h +++ b/chromium/device/fido/make_credential_request_handler.h @@ -27,30 +27,35 @@ namespace device { class FidoAuthenticator; class AuthenticatorMakeCredentialResponse; -using RegisterResponseCallback = base::OnceCallback< - void(FidoReturnCode, base::Optional<AuthenticatorMakeCredentialResponse>)>; +using RegisterResponseCallback = + base::OnceCallback<void(FidoReturnCode, + base::Optional<AuthenticatorMakeCredentialResponse>, + FidoTransportProtocol)>; class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialRequestHandler : public FidoRequestHandler<AuthenticatorMakeCredentialResponse> { public: MakeCredentialRequestHandler( service_manager::Connector* connector, - const base::flat_set<FidoTransportProtocol>& protocols, + const base::flat_set<FidoTransportProtocol>& supported_transports, CtapMakeCredentialRequest request_parameter, AuthenticatorSelectionCriteria authenticator_criteria, RegisterResponseCallback completion_callback); - MakeCredentialRequestHandler( - service_manager::Connector* connector, - const base::flat_set<FidoTransportProtocol>& protocols, - CtapMakeCredentialRequest request_parameter, - AuthenticatorSelectionCriteria authenticator_criteria, - RegisterResponseCallback completion_callback, - AddPlatformAuthenticatorCallback add_platform_authenticator); ~MakeCredentialRequestHandler() override; + // FidoRequestHandlerBase: + void SetPlatformAuthenticatorOrMarkUnavailable( + base::Optional<PlatformAuthenticatorInfo> platform_authenticator_info) + override; + private: // FidoRequestHandlerBase: - void DispatchRequest(FidoAuthenticator* authenticator) final; + void DispatchRequest(FidoAuthenticator* authenticator) override; + + void HandleResponse( + FidoAuthenticator* authenticator, + CtapDeviceResponseCode response_code, + base::Optional<AuthenticatorMakeCredentialResponse> response); CtapMakeCredentialRequest request_parameter_; AuthenticatorSelectionCriteria authenticator_selection_criteria_; diff --git a/chromium/device/fido/make_credential_task.cc b/chromium/device/fido/make_credential_task.cc index 0d823468130..8d53f17c92b 100644 --- a/chromium/device/fido/make_credential_task.cc +++ b/chromium/device/fido/make_credential_task.cc @@ -9,9 +9,6 @@ #include "base/bind.h" #include "device/base/features.h" #include "device/fido/ctap2_device_operation.h" -#include "device/fido/ctap_empty_authenticator_request.h" -#include "device/fido/device_response_converter.h" -#include "device/fido/fido_parsing_utils.h" #include "device/fido/u2f_command_constructor.h" #include "device/fido/u2f_register_operation.h" @@ -61,6 +58,7 @@ void MakeCredentialTask::StartTask() { IsClientPinOptionCompatible(device(), request_parameter_)) { MakeCredential(); } else { + device()->set_supported_protocol(ProtocolVersion::kU2f); U2fRegister(); } } @@ -68,9 +66,7 @@ void MakeCredentialTask::StartTask() { void MakeCredentialTask::MakeCredential() { register_operation_ = std::make_unique<Ctap2DeviceOperation< CtapMakeCredentialRequest, AuthenticatorMakeCredentialResponse>>( - device(), request_parameter_, - base::BindOnce(&MakeCredentialTask::OnCtapMakeCredentialResponseReceived, - weak_factory_.GetWeakPtr()), + device(), request_parameter_, std::move(callback_), base::BindOnce(&ReadCTAPMakeCredentialResponse)); register_operation_->Start(); } @@ -82,33 +78,10 @@ void MakeCredentialTask::U2fRegister() { return; } + DCHECK_EQ(ProtocolVersion::kU2f, device()->supported_protocol()); register_operation_ = std::make_unique<U2fRegisterOperation>( - device(), request_parameter_, - base::BindOnce(&MakeCredentialTask::OnCtapMakeCredentialResponseReceived, - weak_factory_.GetWeakPtr())); + device(), request_parameter_, std::move(callback_)); register_operation_->Start(); } -void MakeCredentialTask::OnCtapMakeCredentialResponseReceived( - CtapDeviceResponseCode return_code, - base::Optional<AuthenticatorMakeCredentialResponse> response_data) { - if (return_code != CtapDeviceResponseCode::kSuccess) { - std::move(callback_).Run(return_code, base::nullopt); - return; - } - - const auto rp_id_hash = - fido_parsing_utils::CreateSHA256Hash(request_parameter_.rp().rp_id()); - - // TODO(martinkr): CheckRpIdHash invocation needs to move into the Request - // handler. See https://crbug.com/863988. - if (!response_data || response_data->GetRpIdHash() != rp_id_hash) { - std::move(callback_).Run(CtapDeviceResponseCode::kCtap2ErrOther, - base::nullopt); - return; - } - - std::move(callback_).Run(return_code, std::move(response_data)); -} - } // namespace device diff --git a/chromium/device/fido/make_credential_task.h b/chromium/device/fido/make_credential_task.h index fc8ae97d776..61e35f6b4ed 100644 --- a/chromium/device/fido/make_credential_task.h +++ b/chromium/device/fido/make_credential_task.h @@ -45,9 +45,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialTask : public FidoTask { void MakeCredential(); void U2fRegister(); - void OnCtapMakeCredentialResponseReceived( - CtapDeviceResponseCode return_code, - base::Optional<AuthenticatorMakeCredentialResponse> response_data); CtapMakeCredentialRequest request_parameter_; std::unique_ptr<RegisterOperation> register_operation_; diff --git a/chromium/device/fido/make_credential_task_unittest.cc b/chromium/device/fido/make_credential_task_unittest.cc index 7c49f6bdc34..b150e876166 100644 --- a/chromium/device/fido/make_credential_task_unittest.cc +++ b/chromium/device/fido/make_credential_task_unittest.cc @@ -107,19 +107,6 @@ TEST_F(FidoMakeCredentialTaskTest, TestRegisterSuccessWithFake) { make_credential_callback_receiver().value()->raw_credential_id().size()); } -TEST_F(FidoMakeCredentialTaskTest, MakeCredentialWithIncorrectRpIdHash) { - auto device = MockFidoDevice::MakeCtap(); - device->ExpectCtap2CommandAndRespondWith( - CtapRequestCommand::kAuthenticatorMakeCredential, - test_data::kTestMakeCredentialResponseWithIncorrectRpIdHash); - - const auto task = CreateMakeCredentialTask(device.get()); - make_credential_callback_receiver().WaitForCallback(); - - EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrOther, - make_credential_callback_receiver().status()); -} - TEST_F(FidoMakeCredentialTaskTest, FallbackToU2fRegisterSuccess) { auto device = MockFidoDevice::MakeU2f(); device->ExpectRequestAndRespondWith( @@ -150,8 +137,7 @@ TEST_F(FidoMakeCredentialTaskTest, TestDefaultU2fRegisterOperationWithoutFlag) { TEST_F(FidoMakeCredentialTaskTest, DefaultToU2fWhenClientPinSet) { AuthenticatorGetInfoResponse device_info( - {ProtocolVersion::kCtap, ProtocolVersion::kU2f}, - fido_parsing_utils::Materialize(kTestDeviceAaguid)); + {ProtocolVersion::kCtap, ProtocolVersion::kU2f}, kTestDeviceAaguid); AuthenticatorSupportedOptions options; options.SetClientPinAvailability( AuthenticatorSupportedOptions::ClientPinAvailability:: @@ -165,6 +151,7 @@ TEST_F(FidoMakeCredentialTaskTest, DefaultToU2fWhenClientPinSet) { const auto task = CreateMakeCredentialTask(device.get()); make_credential_callback_receiver().WaitForCallback(); + EXPECT_EQ(ProtocolVersion::kU2f, device->supported_protocol()); EXPECT_EQ(CtapDeviceResponseCode::kSuccess, make_credential_callback_receiver().status()); EXPECT_TRUE(make_credential_callback_receiver().value()); @@ -172,8 +159,7 @@ TEST_F(FidoMakeCredentialTaskTest, DefaultToU2fWhenClientPinSet) { TEST_F(FidoMakeCredentialTaskTest, EnforceClientPinWhenUserVerificationSet) { AuthenticatorGetInfoResponse device_info( - {ProtocolVersion::kCtap, ProtocolVersion::kU2f}, - fido_parsing_utils::Materialize(kTestDeviceAaguid)); + {ProtocolVersion::kCtap, ProtocolVersion::kU2f}, kTestDeviceAaguid); AuthenticatorSupportedOptions options; options.SetClientPinAvailability( AuthenticatorSupportedOptions::ClientPinAvailability:: diff --git a/chromium/device/fido/mock_fido_device.cc b/chromium/device/fido/mock_fido_device.cc index cf499d5b6cc..27030a57c8f 100644 --- a/chromium/device/fido/mock_fido_device.cc +++ b/chromium/device/fido/mock_fido_device.cc @@ -8,6 +8,7 @@ #include "base/bind.h" #include "base/location.h" +#include "base/strings/strcat.h" #include "base/threading/thread_task_runner_handle.h" #include "components/apdu/apdu_response.h" #include "device/fido/device_response_converter.h" @@ -38,6 +39,29 @@ std::unique_ptr<MockFidoDevice> MockFidoDevice::MakeCtap( std::move(*device_info)); } +// static +std::unique_ptr<MockFidoDevice> +MockFidoDevice::MakeU2fWithGetInfoExpectation() { + auto device = std::make_unique<MockFidoDevice>(); + device->StubGetId(); + device->ExpectCtap2CommandAndRespondWith( + CtapRequestCommand::kAuthenticatorGetInfo, base::nullopt); + return device; +} + +// static +std::unique_ptr<MockFidoDevice> MockFidoDevice::MakeCtapWithGetInfoExpectation( + base::Optional<base::span<const uint8_t>> get_info_response) { + auto device = std::make_unique<MockFidoDevice>(); + device->StubGetId(); + if (!get_info_response) { + get_info_response = test_data::kTestAuthenticatorGetInfoResponse; + } + device->ExpectCtap2CommandAndRespondWith( + CtapRequestCommand::kAuthenticatorGetInfo, std::move(get_info_response)); + return device; +} + // Matcher to compare the fist byte of the incoming requests. MATCHER_P(IsCtap2Command, expected_command, "") { return !arg.empty() && arg[0] == base::strict_cast<uint8_t>(expected_command); @@ -64,10 +88,26 @@ void MockFidoDevice::DeviceTransact(std::vector<uint8_t> command, DeviceTransactPtr(command, cb); } +FidoTransportProtocol MockFidoDevice::DeviceTransport() const { + return transport_protocol_; +} + +base::WeakPtr<FidoDevice> MockFidoDevice::GetWeakPtr() { + return weak_factory_.GetWeakPtr(); +} + void MockFidoDevice::ExpectWinkedAtLeastOnce() { EXPECT_CALL(*this, TryWinkRef(::testing::_)).Times(::testing::AtLeast(1)); } +void MockFidoDevice::StubGetId() { + // Use a counter to keep the device ID unique. + static size_t i = 0; + EXPECT_CALL(*this, GetId()) + .WillRepeatedly( + testing::Return(base::StrCat({"mockdevice", std::to_string(i++)}))); +} + void MockFidoDevice::ExpectCtap2CommandAndRespondWith( CtapRequestCommand command, base::Optional<base::span<const uint8_t>> response, @@ -82,6 +122,14 @@ void MockFidoDevice::ExpectCtap2CommandAndRespondWith( .WillOnce(::testing::WithArg<1>(::testing::Invoke(send_response))); } +void MockFidoDevice::ExpectCtap2CommandAndRespondWithError( + CtapRequestCommand command, + CtapDeviceResponseCode response_code, + base::TimeDelta delay) { + std::array<uint8_t, 1> data{base::strict_cast<uint8_t>(response_code)}; + return ExpectCtap2CommandAndRespondWith(std::move(command), data, delay); +} + void MockFidoDevice::ExpectRequestAndRespondWith( base::span<const uint8_t> request, base::Optional<base::span<const uint8_t>> response, @@ -110,8 +158,9 @@ void MockFidoDevice::ExpectRequestAndDoNotRespond( DeviceTransactPtr(std::move(request_as_vector), ::testing::_)); } -base::WeakPtr<FidoDevice> MockFidoDevice::GetWeakPtr() { - return weak_factory_.GetWeakPtr(); +void MockFidoDevice::SetDeviceTransport( + FidoTransportProtocol transport_protocol) { + transport_protocol_ = transport_protocol; } } // namespace device diff --git a/chromium/device/fido/mock_fido_device.h b/chromium/device/fido/mock_fido_device.h index 0df85aa9b3d..2337341dca9 100644 --- a/chromium/device/fido/mock_fido_device.h +++ b/chromium/device/fido/mock_fido_device.h @@ -7,6 +7,7 @@ #include <stdint.h> +#include <memory> #include <string> #include <vector> @@ -17,15 +18,34 @@ #include "base/time/time.h" #include "device/fido/fido_constants.h" #include "device/fido/fido_device.h" +#include "device/fido/fido_transport_protocol.h" #include "testing/gmock/include/gmock/gmock.h" namespace device { -class MockFidoDevice : public FidoDevice { +class MockFidoDevice : public ::testing::StrictMock<FidoDevice> { public: + // MakeU2f returns a fully initialized U2F device. This represents the state + // after |DiscoverSupportedProtocolAndDeviceInfo| has been called by the + // FidoDiscovery. static std::unique_ptr<MockFidoDevice> MakeU2f(); + // MakeCtap returns a fully initialized CTAP device. This represents the + // state after |DiscoverSupportedProtocolAndDeviceInfo| has been called by + // the FidoDiscovery. static std::unique_ptr<MockFidoDevice> MakeCtap( base::Optional<AuthenticatorGetInfoResponse> device_info = base::nullopt); + // MakeU2fWithDeviceInfoExpectation returns a uninitialized U2F device + // suitable for injecting into a FidoDiscovery, which will determine its + // protocol version by invoking |DiscoverSupportedProtocolAndDeviceInfo|. + static std::unique_ptr<MockFidoDevice> MakeU2fWithGetInfoExpectation(); + // MakeCtapWithDeviceInfoExpectation returns a uninitialized CTAP device + // suitable for injecting into a FidoDiscovery, which will determine its + // protocol version by invoking |DiscoverSupportedProtocolAndDeviceInfo|. If a + // response is supplied, the mock will use that to reply; otherwise it will + // use |test_data::kTestAuthenticatorGetInfoResponse|. + static std::unique_ptr<MockFidoDevice> MakeCtapWithGetInfoExpectation( + base::Optional<base::span<const uint8_t>> get_info_response = + base::nullopt); MockFidoDevice(); MockFidoDevice(ProtocolVersion protocol_version, @@ -46,21 +66,32 @@ class MockFidoDevice : public FidoDevice { MOCK_METHOD2(DeviceTransactPtr, void(const std::vector<uint8_t>& command, DeviceCallback& cb)); void DeviceTransact(std::vector<uint8_t> command, DeviceCallback cb) override; + + // FidoDevice: + FidoTransportProtocol DeviceTransport() const override; + base::WeakPtr<FidoDevice> GetWeakPtr() override; + void ExpectWinkedAtLeastOnce(); void ExpectCtap2CommandAndRespondWith( CtapRequestCommand command, base::Optional<base::span<const uint8_t>> response, base::TimeDelta delay = base::TimeDelta()); + void ExpectCtap2CommandAndRespondWithError( + CtapRequestCommand command, + CtapDeviceResponseCode response_code, + base::TimeDelta delay = base::TimeDelta()); void ExpectRequestAndRespondWith( base::span<const uint8_t> request, base::Optional<base::span<const uint8_t>> response, base::TimeDelta delay = base::TimeDelta()); void ExpectCtap2CommandAndDoNotRespond(CtapRequestCommand command); void ExpectRequestAndDoNotRespond(base::span<const uint8_t> request); - - base::WeakPtr<FidoDevice> GetWeakPtr() override; + void StubGetId(); + void SetDeviceTransport(FidoTransportProtocol transport_protocol); private: + FidoTransportProtocol transport_protocol_ = + FidoTransportProtocol::kUsbHumanInterfaceDevice; base::WeakPtrFactory<FidoDevice> weak_factory_; DISALLOW_COPY_AND_ASSIGN(MockFidoDevice); diff --git a/chromium/device/fido/opaque_attestation_statement.cc b/chromium/device/fido/opaque_attestation_statement.cc index 147bd7db199..69f85b3e6cf 100644 --- a/chromium/device/fido/opaque_attestation_statement.cc +++ b/chromium/device/fido/opaque_attestation_statement.cc @@ -8,20 +8,22 @@ #include "components/cbor/cbor_values.h" +using cbor::CBORValue; + namespace device { OpaqueAttestationStatement::OpaqueAttestationStatement( std::string attestation_format, - cbor::CBORValue attestation_statement_map) + CBORValue attestation_statement_map) : AttestationStatement(std::move(attestation_format)), attestation_statement_map_(std::move(attestation_statement_map)) {} OpaqueAttestationStatement::~OpaqueAttestationStatement() = default; // Returns the deep copied cbor map value of |attestation_statement_map_|. -cbor::CBORValue::MapValue OpaqueAttestationStatement::GetAsCBORMap() const { +CBORValue::MapValue OpaqueAttestationStatement::GetAsCBORMap() const { DCHECK(attestation_statement_map_.is_map()); - cbor::CBORValue::MapValue new_map; + CBORValue::MapValue new_map; new_map.reserve(attestation_statement_map_.GetMap().size()); for (const auto& map_it : attestation_statement_map_.GetMap()) { new_map.try_emplace(new_map.end(), map_it.first.Clone(), @@ -30,6 +32,16 @@ cbor::CBORValue::MapValue OpaqueAttestationStatement::GetAsCBORMap() const { return new_map; } +bool OpaqueAttestationStatement::IsSelfAttestation() { + DCHECK(attestation_statement_map_.is_map()); + const CBORValue::MapValue& m(attestation_statement_map_.GetMap()); + const CBORValue alg("alg"); + const CBORValue sig("sig"); + + return format_ == "packed" && m.size() == 2 && m.count(std::move(alg)) == 1 && + m.count(std::move(sig)) == 1; +} + bool OpaqueAttestationStatement:: IsAttestationCertificateInappropriatelyIdentifying() { return false; diff --git a/chromium/device/fido/opaque_attestation_statement.h b/chromium/device/fido/opaque_attestation_statement.h index 89e281c1c66..e0d1d170a9e 100644 --- a/chromium/device/fido/opaque_attestation_statement.h +++ b/chromium/device/fido/opaque_attestation_statement.h @@ -24,6 +24,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) OpaqueAttestationStatement // AttestationStatement: cbor::CBORValue::MapValue GetAsCBORMap() const override; + bool IsSelfAttestation() override; bool IsAttestationCertificateInappropriatelyIdentifying() override; private: diff --git a/chromium/device/fido/public_key_credential_descriptor.cc b/chromium/device/fido/public_key_credential_descriptor.cc index af48eebcf34..8bce337ce19 100644 --- a/chromium/device/fido/public_key_credential_descriptor.cc +++ b/chromium/device/fido/public_key_credential_descriptor.cc @@ -41,7 +41,22 @@ PublicKeyCredentialDescriptor::CreateFromCBORValue( PublicKeyCredentialDescriptor::PublicKeyCredentialDescriptor( CredentialType credential_type, std::vector<uint8_t> id) - : credential_type_(credential_type), id_(std::move(id)) {} + : PublicKeyCredentialDescriptor( + credential_type, + std::move(id), + {FidoTransportProtocol::kUsbHumanInterfaceDevice, + FidoTransportProtocol::kBluetoothLowEnergy, + FidoTransportProtocol::kNearFieldCommunication, + FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy, + FidoTransportProtocol::kInternal}) {} + +PublicKeyCredentialDescriptor::PublicKeyCredentialDescriptor( + CredentialType credential_type, + std::vector<uint8_t> id, + base::flat_set<FidoTransportProtocol> transports) + : credential_type_(credential_type), + id_(std::move(id)), + transports_(std::move(transports)) {} PublicKeyCredentialDescriptor::PublicKeyCredentialDescriptor( const PublicKeyCredentialDescriptor& other) = default; diff --git a/chromium/device/fido/public_key_credential_descriptor.h b/chromium/device/fido/public_key_credential_descriptor.h index 02bc4389a37..626832666d9 100644 --- a/chromium/device/fido/public_key_credential_descriptor.h +++ b/chromium/device/fido/public_key_credential_descriptor.h @@ -10,9 +10,11 @@ #include <vector> #include "base/component_export.h" +#include "base/containers/flat_set.h" #include "base/optional.h" #include "components/cbor/cbor_values.h" #include "device/fido/fido_constants.h" +#include "device/fido/fido_transport_protocol.h" namespace device { @@ -27,6 +29,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) PublicKeyCredentialDescriptor { PublicKeyCredentialDescriptor(CredentialType credential_type, std::vector<uint8_t> id); + PublicKeyCredentialDescriptor( + CredentialType credential_type, + std::vector<uint8_t> id, + base::flat_set<FidoTransportProtocol> transports); PublicKeyCredentialDescriptor(const PublicKeyCredentialDescriptor& other); PublicKeyCredentialDescriptor(PublicKeyCredentialDescriptor&& other); PublicKeyCredentialDescriptor& operator=( @@ -39,10 +45,14 @@ class COMPONENT_EXPORT(DEVICE_FIDO) PublicKeyCredentialDescriptor { CredentialType credential_type() const { return credential_type_; } const std::vector<uint8_t>& id() const { return id_; } + const base::flat_set<FidoTransportProtocol>& transports() const { + return transports_; + } private: CredentialType credential_type_; std::vector<uint8_t> id_; + base::flat_set<FidoTransportProtocol> transports_; }; } // namespace device diff --git a/chromium/device/fido/scoped_virtual_fido_device.cc b/chromium/device/fido/scoped_virtual_fido_device.cc index 4449bc15249..b0807e34e1b 100644 --- a/chromium/device/fido/scoped_virtual_fido_device.cc +++ b/chromium/device/fido/scoped_virtual_fido_device.cc @@ -58,7 +58,7 @@ ScopedVirtualFidoDevice::~ScopedVirtualFidoDevice() = default; void ScopedVirtualFidoDevice::SetSupportedProtocol( ProtocolVersion supported_protocol) { - supported_protocol_ = ProtocolVersion::kCtap; + supported_protocol_ = supported_protocol; } VirtualFidoDevice::State* ScopedVirtualFidoDevice::mutable_state() { diff --git a/chromium/device/fido/strings/BUILD.gn b/chromium/device/fido/strings/BUILD.gn new file mode 100644 index 00000000000..a07303dc777 --- /dev/null +++ b/chromium/device/fido/strings/BUILD.gn @@ -0,0 +1,66 @@ +# Copyright 2018 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. + +import("//tools/grit/grit_rule.gni") + +grit("strings") { + source = "../fido_strings.grd" + outputs = [ + "grit/fido_strings.h", + "fido_strings_am.pak", + "fido_strings_ar.pak", + "fido_strings_bg.pak", + "fido_strings_bn.pak", + "fido_strings_ca.pak", + "fido_strings_cs.pak", + "fido_strings_da.pak", + "fido_strings_de.pak", + "fido_strings_el.pak", + "fido_strings_en-GB.pak", + "fido_strings_en-US.pak", + "fido_strings_es.pak", + "fido_strings_es-419.pak", + "fido_strings_et.pak", + "fido_strings_fa.pak", + "fido_strings_fake-bidi.pak", + "fido_strings_fi.pak", + "fido_strings_fil.pak", + "fido_strings_fr.pak", + "fido_strings_gu.pak", + "fido_strings_he.pak", + "fido_strings_hi.pak", + "fido_strings_hr.pak", + "fido_strings_hu.pak", + "fido_strings_id.pak", + "fido_strings_it.pak", + "fido_strings_ja.pak", + "fido_strings_kn.pak", + "fido_strings_ko.pak", + "fido_strings_lt.pak", + "fido_strings_lv.pak", + "fido_strings_ml.pak", + "fido_strings_mr.pak", + "fido_strings_ms.pak", + "fido_strings_nl.pak", + "fido_strings_nb.pak", + "fido_strings_pl.pak", + "fido_strings_pt-BR.pak", + "fido_strings_pt-PT.pak", + "fido_strings_ro.pak", + "fido_strings_ru.pak", + "fido_strings_sk.pak", + "fido_strings_sl.pak", + "fido_strings_sr.pak", + "fido_strings_sv.pak", + "fido_strings_sw.pak", + "fido_strings_ta.pak", + "fido_strings_te.pak", + "fido_strings_th.pak", + "fido_strings_tr.pak", + "fido_strings_uk.pak", + "fido_strings_vi.pak", + "fido_strings_zh-CN.pak", + "fido_strings_zh-TW.pak", + ] +} diff --git a/chromium/device/fido/strings/fido_strings_am.xtb b/chromium/device/fido/strings/fido_strings_am.xtb new file mode 100644 index 00000000000..146c2abc3ab --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_am.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="am"> +<translation id="6082592655150610743"><ph name="APP_NAME" /> ላይ ማንነትዎን ያረጋግጡ</translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_ar.xtb b/chromium/device/fido/strings/fido_strings_ar.xtb new file mode 100644 index 00000000000..28a99e14ef7 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_ar.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ar"> +<translation id="6082592655150610743">إثبات هويتك من خلال <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_bg.xtb b/chromium/device/fido/strings/fido_strings_bg.xtb new file mode 100644 index 00000000000..180f20bbd73 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_bg.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="bg"> +<translation id="6082592655150610743">потвърди самоличността ви в(ъв) <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_bn.xtb b/chromium/device/fido/strings/fido_strings_bn.xtb new file mode 100644 index 00000000000..54f47c3eb7c --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_bn.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="bn"> +<translation id="6082592655150610743"><ph name="APP_NAME" />-এ আপনার পরিচয় যাচাই করুন</translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_ca.xtb b/chromium/device/fido/strings/fido_strings_ca.xtb new file mode 100644 index 00000000000..36c2e57e9d0 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_ca.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ca"> +<translation id="6082592655150610743">verificar la teva identitat a <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_cs.xtb b/chromium/device/fido/strings/fido_strings_cs.xtb new file mode 100644 index 00000000000..929ba6d70b0 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_cs.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="cs"> +<translation id="6082592655150610743">ověřit vaši identitu v aplikaci <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_da.xtb b/chromium/device/fido/strings/fido_strings_da.xtb new file mode 100644 index 00000000000..5d86e402974 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_da.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="da"> +<translation id="6082592655150610743">bekræfte din identitet i <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_de.xtb b/chromium/device/fido/strings/fido_strings_de.xtb new file mode 100644 index 00000000000..44b6bb1184e --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_de.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="de"> +<translation id="6082592655150610743">Ihre Identität bei <ph name="APP_NAME" /> bestätigen</translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_el.xtb b/chromium/device/fido/strings/fido_strings_el.xtb new file mode 100644 index 00000000000..0abd0958292 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_el.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="el"> +<translation id="6082592655150610743">επαληθεύσει την ταυτότητά σας στην εφαρμογή <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_en-GB.xtb b/chromium/device/fido/strings/fido_strings_en-GB.xtb new file mode 100644 index 00000000000..55d0186a581 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_en-GB.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="en-GB"> +<translation id="6082592655150610743">verify your identity on <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_es-419.xtb b/chromium/device/fido/strings/fido_strings_es-419.xtb new file mode 100644 index 00000000000..0a248696b53 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_es-419.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="es-419"> +<translation id="6082592655150610743">verificar tu identidad en <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_es.xtb b/chromium/device/fido/strings/fido_strings_es.xtb new file mode 100644 index 00000000000..e7e29f7a8a8 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_es.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="es"> +<translation id="6082592655150610743">verificar tu identidad en <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_et.xtb b/chromium/device/fido/strings/fido_strings_et.xtb new file mode 100644 index 00000000000..a0a94bfc34c --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_et.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="et"> +<translation id="6082592655150610743">rakenduses <ph name="APP_NAME" /> teie isikut kinnitada</translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_fa.xtb b/chromium/device/fido/strings/fido_strings_fa.xtb new file mode 100644 index 00000000000..1f8513b8d7f --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_fa.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="fa"> +<translation id="6082592655150610743">هویتتان را در <ph name="APP_NAME" /> تأیید کنید</translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_fi.xtb b/chromium/device/fido/strings/fido_strings_fi.xtb new file mode 100644 index 00000000000..e64fe8f3e07 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_fi.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="fi"> +<translation id="6082592655150610743">vahvista henkilöllisyytesi sovelluksessa <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_fil.xtb b/chromium/device/fido/strings/fido_strings_fil.xtb new file mode 100644 index 00000000000..dfdecc4771f --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_fil.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="fil"> +<translation id="6082592655150610743">i-verify ang iyong pagkakakilanlan sa <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_fr.xtb b/chromium/device/fido/strings/fido_strings_fr.xtb new file mode 100644 index 00000000000..5ca987ec33f --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_fr.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="fr"> +<translation id="6082592655150610743">valider votre identité sur <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_gu.xtb b/chromium/device/fido/strings/fido_strings_gu.xtb new file mode 100644 index 00000000000..0f8a2d03e62 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_gu.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="gu"> +<translation id="6082592655150610743"><ph name="APP_NAME" /> પર તમારી ઓળખ ચકાસો</translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_hi.xtb b/chromium/device/fido/strings/fido_strings_hi.xtb new file mode 100644 index 00000000000..680928ecff4 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_hi.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="hi"> +<translation id="6082592655150610743"><ph name="APP_NAME" /> पर अपनी पहचान की पुष्टि करें</translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_hr.xtb b/chromium/device/fido/strings/fido_strings_hr.xtb new file mode 100644 index 00000000000..a22898223de --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_hr.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="hr"> +<translation id="6082592655150610743">potvrdi svoj identitet na <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_hu.xtb b/chromium/device/fido/strings/fido_strings_hu.xtb new file mode 100644 index 00000000000..d4f29501ce5 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_hu.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="hu"> +<translation id="6082592655150610743">ellenőrizni az Ön személyazonosságát a következőben: <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_id.xtb b/chromium/device/fido/strings/fido_strings_id.xtb new file mode 100644 index 00000000000..14627d1f62a --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_id.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="id"> +<translation id="6082592655150610743">verifikasi identitas Anda di <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_it.xtb b/chromium/device/fido/strings/fido_strings_it.xtb new file mode 100644 index 00000000000..73fcd973131 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_it.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="it"> +<translation id="6082592655150610743">verificare la tua identità su <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_iw.xtb b/chromium/device/fido/strings/fido_strings_iw.xtb new file mode 100644 index 00000000000..d928685c72c --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_iw.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="iw"> +<translation id="6082592655150610743">אימות הזהות שלך ב-<ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_ja.xtb b/chromium/device/fido/strings/fido_strings_ja.xtb new file mode 100644 index 00000000000..fd0ef497826 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_ja.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ja"> +<translation id="6082592655150610743"><ph name="APP_NAME" /> での本人確認</translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_kn.xtb b/chromium/device/fido/strings/fido_strings_kn.xtb new file mode 100644 index 00000000000..924ca6580da --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_kn.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="kn"> +<translation id="6082592655150610743"><ph name="APP_NAME" /> ನಲ್ಲಿ ನಿಮ್ಮ ಗುರುತನ್ನು ದೃಢೀಕರಿಸಿ</translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_ko.xtb b/chromium/device/fido/strings/fido_strings_ko.xtb new file mode 100644 index 00000000000..6ed0e4e69b3 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_ko.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ko"> +<translation id="6082592655150610743"><ph name="APP_NAME" />에서 본인 확인을 진행</translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_lt.xtb b/chromium/device/fido/strings/fido_strings_lt.xtb new file mode 100644 index 00000000000..1e8ef93bc8a --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_lt.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="lt"> +<translation id="6082592655150610743">patvirtinkite savo tapatybę programoje „<ph name="APP_NAME" />“</translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_lv.xtb b/chromium/device/fido/strings/fido_strings_lv.xtb new file mode 100644 index 00000000000..f5237b591c7 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_lv.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="lv"> +<translation id="6082592655150610743">verificēt jūsu identitāti vietnē <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_ml.xtb b/chromium/device/fido/strings/fido_strings_ml.xtb new file mode 100644 index 00000000000..7930d88649d --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_ml.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ml"> +<translation id="6082592655150610743"><ph name="APP_NAME" />-ൽ നിങ്ങളുടെ ഐഡന്റിറ്റി പരിശോധിച്ച് ഉറപ്പിക്കുക</translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_mr.xtb b/chromium/device/fido/strings/fido_strings_mr.xtb new file mode 100644 index 00000000000..f22441665ff --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_mr.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="mr"> +<translation id="6082592655150610743"><ph name="APP_NAME" /> वर तुमच्या ओळखीची पडताळणी करा</translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_ms.xtb b/chromium/device/fido/strings/fido_strings_ms.xtb new file mode 100644 index 00000000000..d6acdfd7683 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_ms.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ms"> +<translation id="6082592655150610743">sahkan identiti anda pada <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_nl.xtb b/chromium/device/fido/strings/fido_strings_nl.xtb new file mode 100644 index 00000000000..dda9a33246e --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_nl.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="nl"> +<translation id="6082592655150610743">je identiteit te verifiëren op <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_no.xtb b/chromium/device/fido/strings/fido_strings_no.xtb new file mode 100644 index 00000000000..5df8dfadb0e --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_no.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="no"> +<translation id="6082592655150610743">bekrefte identiteten din i <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_pl.xtb b/chromium/device/fido/strings/fido_strings_pl.xtb new file mode 100644 index 00000000000..f8f09b79abe --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_pl.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="pl"> +<translation id="6082592655150610743">zweryfikować Twoją tożsamość na <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_pt-BR.xtb b/chromium/device/fido/strings/fido_strings_pt-BR.xtb new file mode 100644 index 00000000000..584b5bda0ee --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_pt-BR.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="pt-BR"> +<translation id="6082592655150610743">verificar sua identidade no <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_pt-PT.xtb b/chromium/device/fido/strings/fido_strings_pt-PT.xtb new file mode 100644 index 00000000000..e5ecb9dba88 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_pt-PT.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="pt-PT"> +<translation id="6082592655150610743">validar a sua identidade em <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_ro.xtb b/chromium/device/fido/strings/fido_strings_ro.xtb new file mode 100644 index 00000000000..e785fd5dee8 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_ro.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ro"> +<translation id="6082592655150610743">să îți verifice identitatea în <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_ru.xtb b/chromium/device/fido/strings/fido_strings_ru.xtb new file mode 100644 index 00000000000..81f214b0e4d --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_ru.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ru"> +<translation id="6082592655150610743">подтвердить вашу личность в приложении <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_sk.xtb b/chromium/device/fido/strings/fido_strings_sk.xtb new file mode 100644 index 00000000000..1378163263c --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_sk.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="sk"> +<translation id="6082592655150610743">overiť vašu totožnosť v aplikácii <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_sl.xtb b/chromium/device/fido/strings/fido_strings_sl.xtb new file mode 100644 index 00000000000..2736e96002b --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_sl.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="sl"> +<translation id="6082592655150610743">preveriti vašo identiteto v aplikaciji <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_sr.xtb b/chromium/device/fido/strings/fido_strings_sr.xtb new file mode 100644 index 00000000000..51090252433 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_sr.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="sr"> +<translation id="6082592655150610743">верификује ваш идентитет у апликацији <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_sv.xtb b/chromium/device/fido/strings/fido_strings_sv.xtb new file mode 100644 index 00000000000..66c9752e851 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_sv.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="sv"> +<translation id="6082592655150610743">verifiera din identitet på <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_sw.xtb b/chromium/device/fido/strings/fido_strings_sw.xtb new file mode 100644 index 00000000000..2b0b594ea6c --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_sw.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="sw"> +<translation id="6082592655150610743">thibitisha utambulisho wako kwenye <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_ta.xtb b/chromium/device/fido/strings/fido_strings_ta.xtb new file mode 100644 index 00000000000..8f0cfb40958 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_ta.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ta"> +<translation id="6082592655150610743"><ph name="APP_NAME" /> இல் உங்கள் அடையாளத்தைச் சரிபார்க்கவும்</translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_te.xtb b/chromium/device/fido/strings/fido_strings_te.xtb new file mode 100644 index 00000000000..b3dfd4f8e9b --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_te.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="te"> +<translation id="6082592655150610743"><ph name="APP_NAME" />లో మీ గుర్తింపును ధృవీకరించండి</translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_th.xtb b/chromium/device/fido/strings/fido_strings_th.xtb new file mode 100644 index 00000000000..71200bfb27a --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_th.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="th"> +<translation id="6082592655150610743">ยืนยันตัวตนของคุณใน <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_tr.xtb b/chromium/device/fido/strings/fido_strings_tr.xtb new file mode 100644 index 00000000000..fb9120f894f --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_tr.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="tr"> +<translation id="6082592655150610743"><ph name="APP_NAME" /> uygulamasında kimliğinizi doğrulamak</translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_uk.xtb b/chromium/device/fido/strings/fido_strings_uk.xtb new file mode 100644 index 00000000000..50eee6c8d3f --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_uk.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="uk"> +<translation id="6082592655150610743">підтвердити вашу особу в додатку <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_vi.xtb b/chromium/device/fido/strings/fido_strings_vi.xtb new file mode 100644 index 00000000000..94960f9be46 --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_vi.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="vi"> +<translation id="6082592655150610743">xác minh danh tính của bạn trên <ph name="APP_NAME" /></translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_zh-CN.xtb b/chromium/device/fido/strings/fido_strings_zh-CN.xtb new file mode 100644 index 00000000000..94b181300aa --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_zh-CN.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="zh-CN"> +<translation id="6082592655150610743">在“<ph name="APP_NAME" />”上验证您的身份</translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/strings/fido_strings_zh-TW.xtb b/chromium/device/fido/strings/fido_strings_zh-TW.xtb new file mode 100644 index 00000000000..5c9de020e4c --- /dev/null +++ b/chromium/device/fido/strings/fido_strings_zh-TW.xtb @@ -0,0 +1,5 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="zh-TW"> +<translation id="6082592655150610743">向 <ph name="APP_NAME" /> 驗證你的身分</translation> +</translationbundle>
\ No newline at end of file diff --git a/chromium/device/fido/test_callback_receiver.h b/chromium/device/fido/test_callback_receiver.h index 90363783a03..66783570bd1 100644 --- a/chromium/device/fido/test_callback_receiver.h +++ b/chromium/device/fido/test_callback_receiver.h @@ -114,6 +114,20 @@ class StatusAndValueCallbackReceiver } }; +template <class Status, class... Values> +class StatusAndValuesCallbackReceiver + : public TestCallbackReceiver<Status, Values...> { + public: + const Status& status() const { + return std::get<0>(*TestCallbackReceiver<Status, Values...>::result()); + } + + template <size_t I> + const std::tuple_element_t<I, std::tuple<Values...>>& value() const { + return std::get<I + 1>(*TestCallbackReceiver<Status, Values...>::result()); + } +}; + } // namespace test } // namespace device diff --git a/chromium/device/fido/virtual_ctap2_device.cc b/chromium/device/fido/virtual_ctap2_device.cc index 60d4579038c..cdb1c03f48d 100644 --- a/chromium/device/fido/virtual_ctap2_device.cc +++ b/chromium/device/fido/virtual_ctap2_device.cc @@ -109,17 +109,19 @@ std::vector<uint8_t> ConstructSignatureBuffer( } std::vector<uint8_t> ConstructMakeCredentialResponse( - base::span<const uint8_t> attestation_certificate, + const base::Optional<std::vector<uint8_t>> attestation_certificate, base::span<const uint8_t> signature, AuthenticatorData authenticator_data) { cbor::CBORValue::MapValue attestation_map; attestation_map.emplace("alg", -7); attestation_map.emplace("sig", fido_parsing_utils::Materialize(signature)); - cbor::CBORValue::ArrayValue certificate_chain; - certificate_chain.emplace_back( - fido_parsing_utils::Materialize(attestation_certificate)); - attestation_map.emplace("x5c", std::move(certificate_chain)); + if (attestation_certificate) { + cbor::CBORValue::ArrayValue certificate_chain; + certificate_chain.emplace_back(std::move(*attestation_certificate)); + attestation_map.emplace("x5c", std::move(certificate_chain)); + } + AuthenticatorMakeCredentialResponse make_credential_response( AttestationObject( std::move(authenticator_data), @@ -253,9 +255,16 @@ CtapDeviceResponseCode VirtualCtap2Device::OnMakeCredential( std::vector<uint8_t> key_handle(hash.begin(), hash.end()); std::array<uint8_t, 2> sha256_length = {0, crypto::kSHA256Length}; + std::array<uint8_t, 16> kZeroAaguid = {0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + base::span<const uint8_t, 16> aaguid(kDeviceAaguid); + if (mutable_state()->self_attestation && + !mutable_state()->non_zero_aaguid_with_self_attestation) { + aaguid = kZeroAaguid; + } + AttestedCredentialData attested_credential_data( - kDeviceAaguid, sha256_length, key_handle, - ConstructECPublicKey(public_key)); + aaguid, sha256_length, key_handle, ConstructECPublicKey(public_key)); auto authenticator_data = ConstructAuthenticatorData( rp_id_hash, 01ul, std::move(attested_credential_data)); @@ -271,14 +280,17 @@ CtapDeviceResponseCode VirtualCtap2Device::OnMakeCredential( status = Sign(attestation_private_key.get(), std::move(sign_buffer), &sig); DCHECK(status); - auto attestation_cert = GenerateAttestationCertificate( - false /* individual_attestation_requested */); - if (!attestation_cert) { - DLOG(ERROR) << "Failed to generate attestation certificate."; - return CtapDeviceResponseCode::kCtap2ErrOther; + base::Optional<std::vector<uint8_t>> attestation_cert; + if (!mutable_state()->self_attestation) { + attestation_cert = GenerateAttestationCertificate( + false /* individual_attestation_requested */); + if (!attestation_cert) { + DLOG(ERROR) << "Failed to generate attestation certificate."; + return CtapDeviceResponseCode::kCtap2ErrOther; + } } - *response = ConstructMakeCredentialResponse(std::move(*attestation_cert), sig, + *response = ConstructMakeCredentialResponse(std::move(attestation_cert), sig, std::move(authenticator_data)); StoreNewKey(rp_id_hash, key_handle, std::move(private_key)); diff --git a/chromium/device/fido/virtual_fido_device.cc b/chromium/device/fido/virtual_fido_device.cc index 06c5dac3074..6abbdeb45d5 100644 --- a/chromium/device/fido/virtual_fido_device.cc +++ b/chromium/device/fido/virtual_fido_device.cc @@ -158,4 +158,9 @@ std::string VirtualFidoDevice::GetId() const { return "VirtualFidoDevice-" + std::to_string((size_t)this % 0xffe1); } +FidoTransportProtocol VirtualFidoDevice::DeviceTransport() const { + // Virtual device are injected as HID devices. + return FidoTransportProtocol::kUsbHumanInterfaceDevice; +} + } // namespace device diff --git a/chromium/device/fido/virtual_fido_device.h b/chromium/device/fido/virtual_fido_device.h index 92ad511ecd3..06a65f7b870 100644 --- a/chromium/device/fido/virtual_fido_device.h +++ b/chromium/device/fido/virtual_fido_device.h @@ -80,6 +80,15 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice { // If true, causes the response from the device to be invalid. bool simulate_invalid_response = false; + // If true, return a packed self-attestation rather than a generated + // certificate. This only has an effect for a CTAP2 device as + // self-attestation is not defined for CTAP1. + bool self_attestation = false; + + // Only valid if |self_attestation| is true. Causes the AAGUID to be non- + // zero, in violation of the rules for self-attestation. + bool non_zero_aaguid_with_self_attestation = false; + // Adds a registration for the specified credential ID with the application // parameter set to be valid for the given relying party ID (which would // typically be a domain, e.g. "example.com"). @@ -133,6 +142,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice { // FidoDevice: void TryWink(WinkCallback cb) override; std::string GetId() const override; + FidoTransportProtocol DeviceTransport() const override; private: scoped_refptr<State> state_ = base::MakeRefCounted<State>(); diff --git a/chromium/device/gamepad/gamepad_consumer.h b/chromium/device/gamepad/gamepad_consumer.h index f80183d2e55..b91095a5c88 100644 --- a/chromium/device/gamepad/gamepad_consumer.h +++ b/chromium/device/gamepad/gamepad_consumer.h @@ -15,9 +15,11 @@ class DEVICE_GAMEPAD_EXPORT GamepadConsumer { GamepadConsumer(); virtual ~GamepadConsumer(); - virtual void OnGamepadConnected(unsigned index, const Gamepad& gamepad) = 0; - virtual void OnGamepadDisconnected(unsigned index, + virtual void OnGamepadConnected(uint32_t index, const Gamepad& gamepad) = 0; + virtual void OnGamepadDisconnected(uint32_t index, const Gamepad& gamepad) = 0; + virtual void OnGamepadButtonOrAxisChanged(uint32_t index, + const Gamepad& gamepad) = 0; }; } // namespace device diff --git a/chromium/device/gamepad/gamepad_haptics_manager.cc b/chromium/device/gamepad/gamepad_haptics_manager.cc index 656f35014a1..8171275c221 100644 --- a/chromium/device/gamepad/gamepad_haptics_manager.cc +++ b/chromium/device/gamepad/gamepad_haptics_manager.cc @@ -24,7 +24,7 @@ void GamepadHapticsManager::Create( } void GamepadHapticsManager::PlayVibrationEffectOnce( - int pad_index, + uint32_t pad_index, mojom::GamepadHapticEffectType type, mojom::GamepadEffectParametersPtr params, PlayVibrationEffectOnceCallback callback) { @@ -33,7 +33,7 @@ void GamepadHapticsManager::PlayVibrationEffectOnce( } void GamepadHapticsManager::ResetVibrationActuator( - int pad_index, + uint32_t pad_index, ResetVibrationActuatorCallback callback) { GamepadService::GetInstance()->ResetVibrationActuator(pad_index, std::move(callback)); diff --git a/chromium/device/gamepad/gamepad_haptics_manager.h b/chromium/device/gamepad/gamepad_haptics_manager.h index 6c31e36d9b2..a95d65691d2 100644 --- a/chromium/device/gamepad/gamepad_haptics_manager.h +++ b/chromium/device/gamepad/gamepad_haptics_manager.h @@ -20,11 +20,11 @@ class DEVICE_GAMEPAD_EXPORT GamepadHapticsManager static void Create(mojom::GamepadHapticsManagerRequest request); // mojom::GamepadHapticsManager implementation. - void PlayVibrationEffectOnce(int pad_index, + void PlayVibrationEffectOnce(uint32_t pad_index, mojom::GamepadHapticEffectType, mojom::GamepadEffectParametersPtr, PlayVibrationEffectOnceCallback) override; - void ResetVibrationActuator(int pad_index, + void ResetVibrationActuator(uint32_t pad_index, ResetVibrationActuatorCallback) override; private: diff --git a/chromium/device/gamepad/gamepad_monitor.cc b/chromium/device/gamepad/gamepad_monitor.cc index 27ba975651e..63d3d377c5e 100644 --- a/chromium/device/gamepad/gamepad_monitor.cc +++ b/chromium/device/gamepad/gamepad_monitor.cc @@ -27,18 +27,24 @@ void GamepadMonitor::Create(mojom::GamepadMonitorRequest request) { std::move(request)); } -void GamepadMonitor::OnGamepadConnected(unsigned index, +void GamepadMonitor::OnGamepadConnected(uint32_t index, const Gamepad& gamepad) { if (gamepad_observer_) gamepad_observer_->GamepadConnected(index, gamepad); } -void GamepadMonitor::OnGamepadDisconnected(unsigned index, +void GamepadMonitor::OnGamepadDisconnected(uint32_t index, const Gamepad& gamepad) { if (gamepad_observer_) gamepad_observer_->GamepadDisconnected(index, gamepad); } +void GamepadMonitor::OnGamepadButtonOrAxisChanged(uint32_t index, + const Gamepad& gamepad) { + if (gamepad_observer_) + gamepad_observer_->GamepadButtonOrAxisChanged(index, gamepad); +} + void GamepadMonitor::GamepadStartPolling(GamepadStartPollingCallback callback) { DCHECK(!is_started_); is_started_ = true; diff --git a/chromium/device/gamepad/gamepad_monitor.h b/chromium/device/gamepad/gamepad_monitor.h index 7ee56f5b370..8e62bf3a81a 100644 --- a/chromium/device/gamepad/gamepad_monitor.h +++ b/chromium/device/gamepad/gamepad_monitor.h @@ -22,8 +22,10 @@ class DEVICE_GAMEPAD_EXPORT GamepadMonitor : public GamepadConsumer, static void Create(mojom::GamepadMonitorRequest request); // GamepadConsumer implementation. - void OnGamepadConnected(unsigned index, const Gamepad& gamepad) override; - void OnGamepadDisconnected(unsigned index, const Gamepad& gamepad) override; + void OnGamepadConnected(uint32_t index, const Gamepad& gamepad) override; + void OnGamepadDisconnected(uint32_t index, const Gamepad& gamepad) override; + void OnGamepadButtonOrAxisChanged(uint32_t index, + const Gamepad& gamepad) override; // mojom::GamepadMonitor implementation. void GamepadStartPolling(GamepadStartPollingCallback callback) override; diff --git a/chromium/device/gamepad/gamepad_pad_state_provider.cc b/chromium/device/gamepad/gamepad_pad_state_provider.cc index 5c793748ddd..11f1f37e4bd 100644 --- a/chromium/device/gamepad/gamepad_pad_state_provider.cc +++ b/chromium/device/gamepad/gamepad_pad_state_provider.cc @@ -20,7 +20,7 @@ const float kMinAxisResetValue = 0.1f; GamepadPadStateProvider::GamepadPadStateProvider() { pad_states_.reset(new PadState[Gamepads::kItemsLengthCap]); - for (unsigned i = 0; i < Gamepads::kItemsLengthCap; ++i) + for (size_t i = 0; i < Gamepads::kItemsLengthCap; ++i) ClearPadState(pad_states_.get()[i]); } @@ -50,8 +50,8 @@ PadState* GamepadPadStateProvider::GetPadState(GamepadSource source, return empty_slot; } -PadState* GamepadPadStateProvider::GetConnectedPadState(int pad_index) { - if (pad_index < 0 || pad_index >= (int)Gamepads::kItemsLengthCap) +PadState* GamepadPadStateProvider::GetConnectedPadState(uint32_t pad_index) { + if (pad_index >= Gamepads::kItemsLengthCap) return nullptr; PadState& pad_state = pad_states_.get()[pad_index]; diff --git a/chromium/device/gamepad/gamepad_pad_state_provider.h b/chromium/device/gamepad/gamepad_pad_state_provider.h index 0824b93f4db..8ffbfe42ab1 100644 --- a/chromium/device/gamepad/gamepad_pad_state_provider.h +++ b/chromium/device/gamepad/gamepad_pad_state_provider.h @@ -91,7 +91,7 @@ class DEVICE_GAMEPAD_EXPORT GamepadPadStateProvider { // Gets a PadState object for a connected gamepad by specifying its index in // the pad_states_ array. Returns NULL if there is no connected gamepad at // that index. - PadState* GetConnectedPadState(int pad_index); + PadState* GetConnectedPadState(uint32_t pad_index); protected: void ClearPadState(PadState& state); diff --git a/chromium/device/gamepad/gamepad_platform_data_fetcher_linux.cc b/chromium/device/gamepad/gamepad_platform_data_fetcher_linux.cc index a8d351bf757..f3ad3a00db7 100644 --- a/chromium/device/gamepad/gamepad_platform_data_fetcher_linux.cc +++ b/chromium/device/gamepad/gamepad_platform_data_fetcher_linux.cc @@ -288,11 +288,7 @@ void GamepadPlatformDataFetcherLinux::EnumerateSubsystemDevices( } void GamepadPlatformDataFetcherLinux::ReadDeviceData(size_t index) { - // Linker does not like CHECK_LT(index, Gamepads::kItemsLengthCap). =/ - if (index >= Gamepads::kItemsLengthCap) { - CHECK(false); - return; - } + CHECK_LT(index, Gamepads::kItemsLengthCap); GamepadDeviceLinux* device = GetDeviceWithJoydevIndex(index); if (!device) diff --git a/chromium/device/gamepad/gamepad_provider.cc b/chromium/device/gamepad/gamepad_provider.cc index ed7fcfa8ef2..fb42a34430a 100644 --- a/chromium/device/gamepad/gamepad_provider.cc +++ b/chromium/device/gamepad/gamepad_provider.cc @@ -97,7 +97,7 @@ void GamepadProvider::GetCurrentGamepadData(Gamepads* data) { } void GamepadProvider::PlayVibrationEffectOnce( - int pad_index, + uint32_t pad_index, mojom::GamepadHapticEffectType type, mojom::GamepadEffectParametersPtr params, mojom::GamepadHapticsManager::PlayVibrationEffectOnceCallback callback) { @@ -120,7 +120,7 @@ void GamepadProvider::PlayVibrationEffectOnce( } void GamepadProvider::ResetVibrationActuator( - int pad_index, + uint32_t pad_index, mojom::GamepadHapticsManager::ResetVibrationActuatorCallback callback) { PadState* pad_state = GetConnectedPadState(pad_index); if (!pad_state) { @@ -284,7 +284,7 @@ void GamepadProvider::DoPoll() { devices_changed_ = false; } - for (unsigned i = 0; i < Gamepads::kItemsLengthCap; ++i) + for (size_t i = 0; i < Gamepads::kItemsLengthCap; ++i) pad_states_.get()[i].is_active = false; // Loop through each registered data fetcher and poll its gamepad data. @@ -300,7 +300,7 @@ void GamepadProvider::DoPoll() { // Send out disconnect events using the last polled data before we wipe it out // in the mapping step. if (ever_had_user_gesture_) { - for (unsigned i = 0; i < Gamepads::kItemsLengthCap; ++i) { + for (size_t i = 0; i < Gamepads::kItemsLengthCap; ++i) { PadState& state = pad_states_.get()[i]; if (!state.is_newly_active && !state.is_active && @@ -319,7 +319,7 @@ void GamepadProvider::DoPoll() { // Acquire the SeqLock. There is only ever one writer to this data. // See gamepad_shared_buffer.h. gamepad_shared_buffer_->WriteBegin(); - for (unsigned i = 0; i < Gamepads::kItemsLengthCap; ++i) { + for (size_t i = 0; i < Gamepads::kItemsLengthCap; ++i) { PadState& state = pad_states_.get()[i]; // Must run through the map+sanitize here or CheckForUserGesture may fail. MapAndSanitizeGamepadData(&state, &buffer->items[i], sanitize_); @@ -328,7 +328,7 @@ void GamepadProvider::DoPoll() { } if (ever_had_user_gesture_) { - for (unsigned i = 0; i < Gamepads::kItemsLengthCap; ++i) { + for (size_t i = 0; i < Gamepads::kItemsLengthCap; ++i) { PadState& state = pad_states_.get()[i]; if (state.is_newly_active && buffer->items[i].connected) { @@ -348,7 +348,7 @@ void GamepadProvider::DoPoll() { // CheckForUserGesture call above. If we don't clear |is_newly_active| here, // we will notify again for the same gamepad on the next polling cycle. if (did_notify) { - for (unsigned i = 0; i < Gamepads::kItemsLengthCap; ++i) + for (size_t i = 0; i < Gamepads::kItemsLengthCap; ++i) pad_states_.get()[i].is_newly_active = false; } @@ -374,7 +374,7 @@ void GamepadProvider::ScheduleDoPoll() { } void GamepadProvider::OnGamepadConnectionChange(bool connected, - int index, + uint32_t index, const Gamepad& pad) { if (connection_change_client_) connection_change_client_->OnGamepadConnectionChange(connected, index, pad); diff --git a/chromium/device/gamepad/gamepad_provider.h b/chromium/device/gamepad/gamepad_provider.h index 03e31de56c7..35a856ea8e0 100644 --- a/chromium/device/gamepad/gamepad_provider.h +++ b/chromium/device/gamepad/gamepad_provider.h @@ -35,7 +35,7 @@ class GamepadDataFetcher; class DEVICE_GAMEPAD_EXPORT GamepadConnectionChangeClient { public: virtual void OnGamepadConnectionChange(bool connected, - int index, + uint32_t index, const Gamepad& pad) = 0; }; @@ -59,13 +59,13 @@ class DEVICE_GAMEPAD_EXPORT GamepadProvider void GetCurrentGamepadData(Gamepads* data); void PlayVibrationEffectOnce( - int pad_index, + uint32_t pad_index, mojom::GamepadHapticEffectType, mojom::GamepadEffectParametersPtr, mojom::GamepadHapticsManager::PlayVibrationEffectOnceCallback); void ResetVibrationActuator( - int pad_index, + uint32_t pad_index, mojom::GamepadHapticsManager::ResetVibrationActuatorCallback); // Pause and resume the background polling thread. Can be called from any @@ -106,7 +106,9 @@ class DEVICE_GAMEPAD_EXPORT GamepadProvider void DoPoll(); void ScheduleDoPoll(); - void OnGamepadConnectionChange(bool connected, int index, const Gamepad& pad); + void OnGamepadConnectionChange(bool connected, + uint32_t index, + const Gamepad& pad); // Checks the gamepad state to see if the user has interacted with it. Returns // true if any user gesture observers were notified. diff --git a/chromium/device/gamepad/gamepad_service.cc b/chromium/device/gamepad/gamepad_service.cc index 5eab297246d..37aa3070cda 100644 --- a/chromium/device/gamepad/gamepad_service.cc +++ b/chromium/device/gamepad/gamepad_service.cc @@ -72,7 +72,7 @@ void GamepadService::ConsumerBecameActive(device::GamepadConsumer* consumer) { const std::vector<bool>& old_connected_state = consumer_state_it->second; Gamepads gamepads; provider_->GetCurrentGamepadData(&gamepads); - for (unsigned i = 0; i < Gamepads::kItemsLengthCap; ++i) { + for (size_t i = 0; i < Gamepads::kItemsLengthCap; ++i) { const Gamepad& gamepad = gamepads.items[i]; if (gamepad.connected) { info.consumer->OnGamepadConnected(i, gamepad); @@ -109,7 +109,7 @@ void GamepadService::ConsumerBecameInactive(device::GamepadConsumer* consumer) { Gamepads gamepads; provider_->GetCurrentGamepadData(&gamepads); std::vector<bool> connected_state(Gamepads::kItemsLengthCap); - for (unsigned i = 0; i < Gamepads::kItemsLengthCap; ++i) + for (size_t i = 0; i < Gamepads::kItemsLengthCap; ++i) connected_state[i] = gamepads.items[i].connected; inactive_consumer_state_[consumer] = connected_state; } @@ -136,7 +136,7 @@ void GamepadService::Terminate() { } void GamepadService::OnGamepadConnectionChange(bool connected, - int index, + uint32_t index, const Gamepad& pad) { if (connected) { main_thread_task_runner_->PostTask( @@ -149,7 +149,7 @@ void GamepadService::OnGamepadConnectionChange(bool connected, } } -void GamepadService::OnGamepadConnected(int index, const Gamepad& pad) { +void GamepadService::OnGamepadConnected(uint32_t index, const Gamepad& pad) { DCHECK(main_thread_task_runner_->BelongsToCurrentThread()); for (ConsumerSet::iterator it = consumers_.begin(); it != consumers_.end(); @@ -159,7 +159,7 @@ void GamepadService::OnGamepadConnected(int index, const Gamepad& pad) { } } -void GamepadService::OnGamepadDisconnected(int index, const Gamepad& pad) { +void GamepadService::OnGamepadDisconnected(uint32_t index, const Gamepad& pad) { DCHECK(main_thread_task_runner_->BelongsToCurrentThread()); for (ConsumerSet::iterator it = consumers_.begin(); it != consumers_.end(); @@ -170,7 +170,7 @@ void GamepadService::OnGamepadDisconnected(int index, const Gamepad& pad) { } void GamepadService::PlayVibrationEffectOnce( - int pad_index, + uint32_t pad_index, mojom::GamepadHapticEffectType type, mojom::GamepadEffectParametersPtr params, mojom::GamepadHapticsManager::PlayVibrationEffectOnceCallback callback) { @@ -187,7 +187,7 @@ void GamepadService::PlayVibrationEffectOnce( } void GamepadService::ResetVibrationActuator( - int pad_index, + uint32_t pad_index, mojom::GamepadHapticsManager::ResetVibrationActuatorCallback callback) { DCHECK(main_thread_task_runner_->BelongsToCurrentThread()); @@ -220,7 +220,7 @@ void GamepadService::OnUserGesture() { info.did_observe_user_gesture = true; Gamepads gamepads; provider_->GetCurrentGamepadData(&gamepads); - for (unsigned i = 0; i < Gamepads::kItemsLengthCap; ++i) { + for (size_t i = 0; i < Gamepads::kItemsLengthCap; ++i) { const Gamepad& pad = gamepads.items[i]; if (pad.connected) info.consumer->OnGamepadConnected(i, pad); diff --git a/chromium/device/gamepad/gamepad_service.h b/chromium/device/gamepad/gamepad_service.h index 74857864674..12723ac4b79 100644 --- a/chromium/device/gamepad/gamepad_service.h +++ b/chromium/device/gamepad/gamepad_service.h @@ -77,16 +77,16 @@ class DEVICE_GAMEPAD_EXPORT GamepadService void Terminate(); // Called on IO thread when a gamepad is connected. - void OnGamepadConnected(int index, const Gamepad& pad); + void OnGamepadConnected(uint32_t index, const Gamepad& pad); // Called on IO thread when a gamepad is disconnected. - void OnGamepadDisconnected(int index, const Gamepad& pad); + void OnGamepadDisconnected(uint32_t index, const Gamepad& pad); // Request playback of a haptic effect on the specified gamepad. Once effect // playback is complete or is preempted by a different effect, the callback // will be called. void PlayVibrationEffectOnce( - int pad_index, + uint32_t pad_index, mojom::GamepadHapticEffectType, mojom::GamepadEffectParametersPtr, mojom::GamepadHapticsManager::PlayVibrationEffectOnceCallback); @@ -95,7 +95,7 @@ class DEVICE_GAMEPAD_EXPORT GamepadService // effects are currently being played, they are preempted and vibration is // stopped. void ResetVibrationActuator( - int pad_index, + uint32_t pad_index, mojom::GamepadHapticsManager::ResetVibrationActuatorCallback); private: @@ -116,7 +116,7 @@ class DEVICE_GAMEPAD_EXPORT GamepadService void OnUserGesture(); void OnGamepadConnectionChange(bool connected, - int index, + uint32_t index, const Gamepad& pad) override; void SetSanitizationEnabled(bool sanitize); diff --git a/chromium/device/gamepad/gamepad_service_unittest.cc b/chromium/device/gamepad/gamepad_service_unittest.cc index b8d9b9d3381..a2a72fc72b4 100644 --- a/chromium/device/gamepad/gamepad_service_unittest.cc +++ b/chromium/device/gamepad/gamepad_service_unittest.cc @@ -25,12 +25,14 @@ class ConnectionListener : public device::GamepadConsumer { public: ConnectionListener() { ClearCounters(); } - void OnGamepadConnected(unsigned index, const Gamepad& gamepad) override { + void OnGamepadConnected(uint32_t index, const Gamepad& gamepad) override { connected_counter_++; } - void OnGamepadDisconnected(unsigned index, const Gamepad& gamepad) override { + void OnGamepadDisconnected(uint32_t index, const Gamepad& gamepad) override { disconnected_counter_++; } + void OnGamepadButtonOrAxisChanged(uint32_t index, + const Gamepad& gamepad) override {} void ClearCounters() { connected_counter_ = 0; diff --git a/chromium/device/gamepad/gamepad_test_helpers.cc b/chromium/device/gamepad/gamepad_test_helpers.cc index 8edd6068967..f6147ca579b 100644 --- a/chromium/device/gamepad/gamepad_test_helpers.cc +++ b/chromium/device/gamepad/gamepad_test_helpers.cc @@ -21,7 +21,7 @@ void MockGamepadDataFetcher::GetGamepadData(bool devices_changed_hint) { { base::AutoLock lock(lock_); - for (unsigned int i = 0; i < Gamepads::kItemsLengthCap; ++i) { + for (size_t i = 0; i < Gamepads::kItemsLengthCap; ++i) { if (test_data_.items[i].connected) { PadState* pad = GetPadState(i); if (pad) diff --git a/chromium/device/gamepad/gamepad_user_gesture.cc b/chromium/device/gamepad/gamepad_user_gesture.cc index 6ae7a31421b..6fdf0a52bbf 100644 --- a/chromium/device/gamepad/gamepad_user_gesture.cc +++ b/chromium/device/gamepad/gamepad_user_gesture.cc @@ -18,7 +18,7 @@ const float kAxisMoveAmountThreshold = 0.5; namespace device { bool GamepadsHaveUserGesture(const Gamepads& gamepads) { - for (unsigned int i = 0; i < Gamepads::kItemsLengthCap; i++) { + for (size_t i = 0; i < Gamepads::kItemsLengthCap; i++) { const Gamepad& pad = gamepads.items[i]; // If the device is physically connected, then check the buttons and axes @@ -33,14 +33,13 @@ bool GamepadsHaveUserGesture(const Gamepads& gamepads) { if (pad.display_id != 0) return true; - for (unsigned int button_index = 0; button_index < pad.buttons_length; + for (size_t button_index = 0; button_index < pad.buttons_length; button_index++) { if (pad.buttons[button_index].pressed) return true; } - for (unsigned int axes_index = 0; axes_index < pad.axes_length; - axes_index++) { + for (size_t axes_index = 0; axes_index < pad.axes_length; axes_index++) { if (fabs(pad.axes[axes_index]) > kAxisMoveAmountThreshold) return true; } diff --git a/chromium/device/gamepad/public/cpp/BUILD.gn b/chromium/device/gamepad/public/cpp/BUILD.gn index 26af5f7d3fd..18a0da7a3cb 100644 --- a/chromium/device/gamepad/public/cpp/BUILD.gn +++ b/chromium/device/gamepad/public/cpp/BUILD.gn @@ -17,6 +17,7 @@ source_set("shared_with_blink") { sources = [ "gamepad.cc", "gamepad.h", + "gamepads.cc", "gamepads.h", ] # Do not add deps here per the above comment. diff --git a/chromium/device/gamepad/public/cpp/gamepad.cc b/chromium/device/gamepad/public/cpp/gamepad.cc index bd5bf4ed77c..1eec089af6c 100644 --- a/chromium/device/gamepad/public/cpp/gamepad.cc +++ b/chromium/device/gamepad/public/cpp/gamepad.cc @@ -6,6 +6,11 @@ namespace device { +const size_t Gamepad::kIdLengthCap; +const size_t Gamepad::kMappingLengthCap; +const size_t Gamepad::kAxesLengthCap; +const size_t Gamepad::kButtonsLengthCap; + Gamepad::Gamepad() : connected(false), timestamp(0), diff --git a/chromium/device/gamepad/public/cpp/gamepad_features.cc b/chromium/device/gamepad/public/cpp/gamepad_features.cc index 75dfa028628..9e74a134c95 100644 --- a/chromium/device/gamepad/public/cpp/gamepad_features.cc +++ b/chromium/device/gamepad/public/cpp/gamepad_features.cc @@ -33,13 +33,35 @@ size_t OverrideIntervalIfValid(base::StringPiece param_value, } // namespace +// Enables gamepadbuttondown, gamepadbuttonup, gamepadbuttonchange, +// gamepadaxismove non-standard gamepad events. +const base::Feature kEnableGamepadButtonAxisEvents{ + "EnableGamepadButtonAxisEvents", base::FEATURE_DISABLED_BY_DEFAULT}; + +// Overrides the gamepad polling interval. const base::Feature kGamepadPollingInterval{"GamepadPollingInterval", base::FEATURE_DISABLED_BY_DEFAULT}; const char kGamepadPollingIntervalParamKey[] = "interval-ms"; +bool AreGamepadButtonAxisEventsEnabled() { + // Check if button and axis events are enabled by a field trial. + if (base::FeatureList::IsEnabled(kEnableGamepadButtonAxisEvents)) + return true; + + // Check if button and axis events are enabled by a command-line flag. + base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + if (command_line && + command_line->HasSwitch(switches::kEnableGamepadButtonAxisEvents)) { + return true; + } + + return false; +} + size_t GetGamepadPollingInterval() { - size_t polling_interval = kPollingIntervalMillisecondsMax; + // Default to the minimum polling interval. + size_t polling_interval = kPollingIntervalMillisecondsMin; // Check if the polling interval is overridden by a field trial. if (base::FeatureList::IsEnabled(kGamepadPollingInterval)) { diff --git a/chromium/device/gamepad/public/cpp/gamepad_features.h b/chromium/device/gamepad/public/cpp/gamepad_features.h index 462c501ec6d..e58cc7f0e30 100644 --- a/chromium/device/gamepad/public/cpp/gamepad_features.h +++ b/chromium/device/gamepad/public/cpp/gamepad_features.h @@ -9,9 +9,11 @@ namespace features { +extern const base::Feature kEnableGamepadButtonAxisEvents; extern const base::Feature kGamepadPollingInterval; extern const char kGamepadPollingIntervalParamKey[]; +bool AreGamepadButtonAxisEventsEnabled(); size_t GetGamepadPollingInterval(); } // namespace features diff --git a/chromium/device/gamepad/public/mojom/gamepad_mojom_traits_unittest.cc b/chromium/device/gamepad/public/cpp/gamepad_mojom_traits_unittest.cc index fc951a3bb9b..69fdd9808ee 100644 --- a/chromium/device/gamepad/public/mojom/gamepad_mojom_traits_unittest.cc +++ b/chromium/device/gamepad/public/cpp/gamepad_mojom_traits_unittest.cc @@ -2,15 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include <utility> - +#include "device/gamepad/public/cpp/gamepad_mojom_traits.h" #include "base/message_loop/message_loop.h" -#include "base/run_loop.h" -#include "base/time/time.h" #include "device/gamepad/public/cpp/gamepad.h" -#include "device/gamepad/public/mojom/gamepad_mojom_traits_test.mojom.h" -#include "mojo/public/cpp/bindings/binding.h" -#include "mojo/public/cpp/bindings/interface_request.h" +#include "device/gamepad/public/mojom/gamepad.mojom.h" +#include "mojo/public/cpp/test_support/test_utils.h" #include "testing/gtest/include/gtest/gtest.h" namespace device { @@ -163,72 +159,51 @@ bool isWebGamepadEqual(const Gamepad& send, const Gamepad& echo) { } return true; } - -void ExpectWebGamepad(const Gamepad& send, - base::OnceClosure closure, - const Gamepad& echo) { - EXPECT_EQ(true, isWebGamepadEqual(send, echo)); - std::move(closure).Run(); -} - } // namespace -class GamepadStructTraitsTest : public testing::Test, - public mojom::GamepadStructTraitsTest { +class GamepadStructTraitsTest : public testing::Test { protected: - GamepadStructTraitsTest() : binding_(this) {} - - void PassGamepad(const Gamepad& send, PassGamepadCallback callback) override { - std::move(callback).Run(send); - } - - mojom::GamepadStructTraitsTestPtr GetGamepadStructTraitsTestProxy() { - mojom::GamepadStructTraitsTestPtr proxy; - binding_.Bind(mojo::MakeRequest(&proxy)); - return proxy; - } + GamepadStructTraitsTest() {} private: base::MessageLoop message_loop_; - mojo::Binding<mojom::GamepadStructTraitsTest> binding_; DISALLOW_COPY_AND_ASSIGN(GamepadStructTraitsTest); }; TEST_F(GamepadStructTraitsTest, GamepadCommon) { - Gamepad send = GetWebGamepadInstance(GamepadCommon); - base::RunLoop loop; - mojom::GamepadStructTraitsTestPtr proxy = GetGamepadStructTraitsTestProxy(); - proxy->PassGamepad( - send, base::BindOnce(&ExpectWebGamepad, send, loop.QuitClosure())); - loop.Run(); + Gamepad gamepad_in = GetWebGamepadInstance(GamepadCommon); + Gamepad gamepad_out; + + ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Gamepad>( + &gamepad_in, &gamepad_out)); + EXPECT_EQ(true, isWebGamepadEqual(gamepad_in, gamepad_out)); } TEST_F(GamepadStructTraitsTest, GamepadPose_HasOrientation) { - Gamepad send = GetWebGamepadInstance(GamepadPose_HasOrientation); - base::RunLoop loop; - mojom::GamepadStructTraitsTestPtr proxy = GetGamepadStructTraitsTestProxy(); - proxy->PassGamepad( - send, base::BindOnce(&ExpectWebGamepad, send, loop.QuitClosure())); - loop.Run(); + Gamepad gamepad_in = GetWebGamepadInstance(GamepadPose_HasOrientation); + Gamepad gamepad_out; + + ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Gamepad>( + &gamepad_in, &gamepad_out)); + EXPECT_EQ(true, isWebGamepadEqual(gamepad_in, gamepad_out)); } TEST_F(GamepadStructTraitsTest, GamepadPose_HasPosition) { - Gamepad send = GetWebGamepadInstance(GamepadPose_HasPosition); - base::RunLoop loop; - mojom::GamepadStructTraitsTestPtr proxy = GetGamepadStructTraitsTestProxy(); - proxy->PassGamepad( - send, base::BindOnce(&ExpectWebGamepad, send, loop.QuitClosure())); - loop.Run(); + Gamepad gamepad_in = GetWebGamepadInstance(GamepadPose_HasPosition); + Gamepad gamepad_out; + + ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Gamepad>( + &gamepad_in, &gamepad_out)); + EXPECT_EQ(true, isWebGamepadEqual(gamepad_in, gamepad_out)); } TEST_F(GamepadStructTraitsTest, GamepadPose_Null) { - Gamepad send = GetWebGamepadInstance(GamepadPose_Null); - base::RunLoop loop; - mojom::GamepadStructTraitsTestPtr proxy = GetGamepadStructTraitsTestProxy(); - proxy->PassGamepad( - send, base::BindOnce(&ExpectWebGamepad, send, loop.QuitClosure())); - loop.Run(); -} + Gamepad gamepad_in = GetWebGamepadInstance(GamepadPose_Null); + Gamepad gamepad_out; + ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::Gamepad>( + &gamepad_in, &gamepad_out)); + EXPECT_EQ(true, isWebGamepadEqual(gamepad_in, gamepad_out)); +} } // namespace device diff --git a/chromium/device/gamepad/public/cpp/gamepad_switches.cc b/chromium/device/gamepad/public/cpp/gamepad_switches.cc index 1dd28a0717b..9185c7726b8 100644 --- a/chromium/device/gamepad/public/cpp/gamepad_switches.cc +++ b/chromium/device/gamepad/public/cpp/gamepad_switches.cc @@ -6,6 +6,11 @@ namespace switches { +// Enables gamepadbuttondown, gamepadbuttonup, gamepadbuttonchange, +// gamepadaxismove non-standard gamepad events. +const char kEnableGamepadButtonAxisEvents[] = + "enable-gamepad-button-axis-events"; + // Overrides the gamepad polling interval. Decreasing the interval improves // input latency of buttons and axes but may negatively affect performance due // to more CPU time spent in the input polling thread. diff --git a/chromium/device/gamepad/public/cpp/gamepad_switches.h b/chromium/device/gamepad/public/cpp/gamepad_switches.h index 18460ec70f1..bbbbb18ea07 100644 --- a/chromium/device/gamepad/public/cpp/gamepad_switches.h +++ b/chromium/device/gamepad/public/cpp/gamepad_switches.h @@ -9,6 +9,7 @@ namespace switches { // All switches in alphabetical order. The switches should be documented // alongside the definition of their values in the .cc file. +extern const char kEnableGamepadButtonAxisEvents[]; extern const char kGamepadPollingInterval[]; } // namespace switches diff --git a/chromium/device/gamepad/public/cpp/gamepads.cc b/chromium/device/gamepad/public/cpp/gamepads.cc new file mode 100644 index 00000000000..82f8c70eb4f --- /dev/null +++ b/chromium/device/gamepad/public/cpp/gamepads.cc @@ -0,0 +1,11 @@ +// Copyright 2018 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 "device/gamepad/public/cpp/gamepads.h" + +namespace device { + +const size_t Gamepads::kItemsLengthCap; + +} // namespace device diff --git a/chromium/device/gamepad/public/mojom/BUILD.gn b/chromium/device/gamepad/public/mojom/BUILD.gn index a44ae8db51a..1bd367aed26 100644 --- a/chromium/device/gamepad/public/mojom/BUILD.gn +++ b/chromium/device/gamepad/public/mojom/BUILD.gn @@ -16,13 +16,3 @@ mojom_component("mojom") { output_prefix = "gamepad_mojom" macro_prefix = "GAMEPAD_MOJOM" } - -mojom("gamepad_mojom_traits_test") { - sources = [ - "gamepad_mojom_traits_test.mojom", - ] - - public_deps = [ - ":mojom", - ] -} diff --git a/chromium/device/gamepad/public/mojom/gamepad.mojom b/chromium/device/gamepad/public/mojom/gamepad.mojom index 415395f788c..1d51cdac13d 100644 --- a/chromium/device/gamepad/public/mojom/gamepad.mojom +++ b/chromium/device/gamepad/public/mojom/gamepad.mojom @@ -67,8 +67,19 @@ struct Gamepad { }; interface GamepadObserver { - GamepadConnected(int32 index, Gamepad gamepad); - GamepadDisconnected(int32 index, Gamepad gamepad); + // Called when a gamepad is connected. |index| is the index of the gamepad in + // the gamepad array, and |gamepad| is a reference to the connected gamepad. + GamepadConnected(uint32 index, Gamepad gamepad); + + // Called when a gamepad is disconnected. |index| is the former index of the + // gamepad in the gamepad array, and |gamepad| is a reference to the + // connected gamepad. + GamepadDisconnected(uint32 index, Gamepad gamepad); + + // Called when a button or axis is changed on a connected gamepad. |index| is + // the index of the gamepad in the gamepad array, and |gamepad| is a reference + // to the gamepad. + GamepadButtonOrAxisChanged(uint32 index, Gamepad gamepad); }; // Asks the browser process to start polling, and return a shared memory @@ -102,9 +113,9 @@ enum GamepadHapticsResult { }; interface GamepadHapticsManager { - PlayVibrationEffectOnce(int32 pad_index, + PlayVibrationEffectOnce(uint32 pad_index, GamepadHapticEffectType type, GamepadEffectParameters params) => (GamepadHapticsResult result); - ResetVibrationActuator(int32 pad_index) => (GamepadHapticsResult result); + ResetVibrationActuator(uint32 pad_index) => (GamepadHapticsResult result); }; diff --git a/chromium/device/gamepad/public/mojom/gamepad_mojom_traits_test.mojom b/chromium/device/gamepad/public/mojom/gamepad_mojom_traits_test.mojom deleted file mode 100644 index 2dcc07855db..00000000000 --- a/chromium/device/gamepad/public/mojom/gamepad_mojom_traits_test.mojom +++ /dev/null @@ -1,11 +0,0 @@ -// 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. - -module device.mojom; - -import "device/gamepad/public/mojom/gamepad.mojom"; - -interface GamepadStructTraitsTest { - PassGamepad(Gamepad send) => (Gamepad echo); -}; diff --git a/chromium/device/serial/BUILD.gn b/chromium/device/serial/BUILD.gn index 77132bfc86d..0ab6e1acc95 100644 --- a/chromium/device/serial/BUILD.gn +++ b/chromium/device/serial/BUILD.gn @@ -20,7 +20,6 @@ if (is_win || is_linux || is_mac) { ":test_support", "//device:device_unittests", "//services/device/serial", - "//tools/battor_agent:battor_agent_lib", ] output_name = "device_serial" @@ -78,19 +77,4 @@ if (is_win || is_linux || is_mac) { ] } } - - static_library("test_support") { - # TODO(leonhsl): Merge necessary parts of TestSerialIoHandler into - # battor_connection_impl_unittest.cc to hide serial impl completely. - visibility = [ "//tools/battor_agent:battor_agent_unittests" ] - - sources = [ - "test_serial_io_handler.cc", - "test_serial_io_handler.h", - ] - - public_deps = [ - ":serial", - ] - } } diff --git a/chromium/device/serial/serial_io_handler.cc b/chromium/device/serial/serial_io_handler.cc index de1d8c178d7..e8219537e08 100644 --- a/chromium/device/serial/serial_io_handler.cc +++ b/chromium/device/serial/serial_io_handler.cc @@ -11,8 +11,8 @@ #include "base/files/file_path.h" #include "base/location.h" #include "base/strings/string_util.h" -#include "base/task_scheduler/post_task.h" -#include "base/task_scheduler/task_traits.h" +#include "base/task/post_task.h" +#include "base/task/task_traits.h" #include "build/build_config.h" #if defined(OS_CHROMEOS) diff --git a/chromium/device/serial/test_serial_io_handler.cc b/chromium/device/serial/test_serial_io_handler.cc deleted file mode 100644 index c04c2a95606..00000000000 --- a/chromium/device/serial/test_serial_io_handler.cc +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2014 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 "device/serial/test_serial_io_handler.h" - -#include <stddef.h> -#include <stdint.h> - -#include <algorithm> - -#include "base/bind.h" -#include "services/device/public/mojom/serial.mojom.h" - -namespace device { - -TestSerialIoHandler::TestSerialIoHandler() - : SerialIoHandler(NULL), - opened_(false), - dtr_(false), - rts_(false), - flushes_(0) {} - -scoped_refptr<SerialIoHandler> TestSerialIoHandler::Create() { - return scoped_refptr<SerialIoHandler>(new TestSerialIoHandler); -} - -void TestSerialIoHandler::Open(const std::string& port, - const mojom::SerialConnectionOptions& options, - OpenCompleteCallback callback) { - DCHECK(!opened_); - opened_ = true; - ConfigurePort(options); - std::move(callback).Run(true); -} - -void TestSerialIoHandler::ReadImpl() { - if (!pending_read_buffer()) - return; - if (buffer_.empty()) - return; - - size_t num_bytes = - std::min(buffer_.size(), static_cast<size_t>(pending_read_buffer_len())); - memcpy(pending_read_buffer(), buffer_.data(), num_bytes); - buffer_.erase(buffer_.begin(), buffer_.begin() + num_bytes); - ReadCompleted(static_cast<uint32_t>(num_bytes), - mojom::SerialReceiveError::NONE); -} - -void TestSerialIoHandler::CancelReadImpl() { - ReadCompleted(0, read_cancel_reason()); -} - -void TestSerialIoHandler::WriteImpl() { - if (send_callback_) { - std::move(send_callback_).Run(); - return; - } - buffer_.insert(buffer_.end(), pending_write_buffer(), - pending_write_buffer() + pending_write_buffer_len()); - WriteCompleted(pending_write_buffer_len(), mojom::SerialSendError::NONE); - if (pending_read_buffer()) - ReadImpl(); -} - -void TestSerialIoHandler::CancelWriteImpl() { - WriteCompleted(0, write_cancel_reason()); -} - -bool TestSerialIoHandler::ConfigurePortImpl() { - info_.bitrate = options().bitrate; - info_.data_bits = options().data_bits; - info_.parity_bit = options().parity_bit; - info_.stop_bits = options().stop_bits; - info_.cts_flow_control = options().cts_flow_control; - return true; -} - -mojom::SerialDeviceControlSignalsPtr TestSerialIoHandler::GetControlSignals() - const { - auto signals = mojom::SerialDeviceControlSignals::New(); - *signals = device_control_signals_; - return signals; -} - -mojom::SerialConnectionInfoPtr TestSerialIoHandler::GetPortInfo() const { - auto info = mojom::SerialConnectionInfo::New(); - *info = info_; - return info; -} - -bool TestSerialIoHandler::Flush() const { - flushes_++; - return true; -} - -bool TestSerialIoHandler::SetControlSignals( - const mojom::SerialHostControlSignals& signals) { - if (signals.has_dtr) - dtr_ = signals.dtr; - if (signals.has_rts) - rts_ = signals.rts; - return true; -} - -bool TestSerialIoHandler::SetBreak() { - return true; -} - -bool TestSerialIoHandler::ClearBreak() { - return true; -} - -void TestSerialIoHandler::ForceReceiveError( - device::mojom::SerialReceiveError error) { - ReadCompleted(0, error); -} - -void TestSerialIoHandler::ForceSendError(device::mojom::SerialSendError error) { - WriteCompleted(0, error); -} - -TestSerialIoHandler::~TestSerialIoHandler() = default; - -} // namespace device diff --git a/chromium/device/serial/test_serial_io_handler.h b/chromium/device/serial/test_serial_io_handler.h deleted file mode 100644 index d60be3a7db8..00000000000 --- a/chromium/device/serial/test_serial_io_handler.h +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2014 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. - -#ifndef DEVICE_SERIAL_TEST_SERIAL_IO_HANDLER_H_ -#define DEVICE_SERIAL_TEST_SERIAL_IO_HANDLER_H_ - -#include <string> - -#include "base/callback.h" -#include "base/macros.h" -#include "device/serial/serial_io_handler.h" -#include "services/device/public/mojom/serial.mojom.h" - -namespace device { - -class TestSerialIoHandler : public SerialIoHandler { - public: - TestSerialIoHandler(); - - static scoped_refptr<SerialIoHandler> Create(); - - // SerialIoHandler overrides. - void Open(const std::string& port, - const mojom::SerialConnectionOptions& options, - OpenCompleteCallback callback) override; - void ReadImpl() override; - void CancelReadImpl() override; - void WriteImpl() override; - void CancelWriteImpl() override; - bool ConfigurePortImpl() override; - mojom::SerialDeviceControlSignalsPtr GetControlSignals() const override; - mojom::SerialConnectionInfoPtr GetPortInfo() const override; - bool Flush() const override; - bool SetControlSignals( - const mojom::SerialHostControlSignals& signals) override; - bool SetBreak() override; - bool ClearBreak() override; - void ForceReceiveError(device::mojom::SerialReceiveError error); - void ForceSendError(device::mojom::SerialSendError error); - - mojom::SerialConnectionInfo* connection_info() { return &info_; } - mojom::SerialDeviceControlSignals* device_control_signals() { - return &device_control_signals_; - } - bool dtr() { return dtr_; } - bool rts() { return rts_; } - int flushes() { return flushes_; } - // This callback will be called when this IoHandler processes its next write, - // instead of the normal behavior of echoing the data to reads. - void set_send_callback(base::OnceClosure callback) { - send_callback_ = std::move(callback); - } - - protected: - ~TestSerialIoHandler() override; - - private: - bool opened_; - mojom::SerialConnectionInfo info_; - mojom::SerialDeviceControlSignals device_control_signals_; - bool dtr_; - bool rts_; - mutable int flushes_; - std::vector<uint8_t> buffer_; - base::OnceClosure send_callback_; - - DISALLOW_COPY_AND_ASSIGN(TestSerialIoHandler); -}; - -} // namespace device - -#endif // DEVICE_SERIAL_TEST_SERIAL_IO_HANDLER_H_ diff --git a/chromium/device/usb/BUILD.gn b/chromium/device/usb/BUILD.gn index 5b183fcc4c8..3248fe6b4ca 100644 --- a/chromium/device/usb/BUILD.gn +++ b/chromium/device/usb/BUILD.gn @@ -81,6 +81,9 @@ static_library("usb") { if (is_win || is_mac) { sources += [ + "scoped_libusb_device_handle.cc", + "scoped_libusb_device_handle.h", + "scoped_libusb_device_ref.cc", "scoped_libusb_device_ref.h", "usb_context.cc", "usb_context.h", diff --git a/chromium/device/usb/mojo/BUILD.gn b/chromium/device/usb/mojo/BUILD.gn index 0a556a864c5..f3f3ee94406 100644 --- a/chromium/device/usb/mojo/BUILD.gn +++ b/chromium/device/usb/mojo/BUILD.gn @@ -8,8 +8,6 @@ source_set("mojo") { "device_impl.h", "device_manager_impl.cc", "device_manager_impl.h", - "permission_provider.cc", - "permission_provider.h", "type_converters.cc", "type_converters.h", ] diff --git a/chromium/device/usb/mojo/device_impl.cc b/chromium/device/usb/mojo/device_impl.cc index 4de3ee0a35a..4c53767435e 100644 --- a/chromium/device/usb/mojo/device_impl.cc +++ b/chromium/device/usb/mojo/device_impl.cc @@ -103,10 +103,9 @@ void OnIsochronousTransferOut( // static void DeviceImpl::Create(scoped_refptr<device::UsbDevice> device, - base::WeakPtr<PermissionProvider> permission_provider, - mojom::UsbDeviceRequest request) { - auto* device_impl = - new DeviceImpl(std::move(device), std::move(permission_provider)); + mojom::UsbDeviceRequest request, + mojom::UsbDeviceClientPtr client) { + auto* device_impl = new DeviceImpl(std::move(device), std::move(client)); device_impl->binding_ = mojo::MakeStrongBinding(base::WrapUnique(device_impl), std::move(request)); } @@ -116,10 +115,10 @@ DeviceImpl::~DeviceImpl() { } DeviceImpl::DeviceImpl(scoped_refptr<device::UsbDevice> device, - base::WeakPtr<PermissionProvider> permission_provider) + mojom::UsbDeviceClientPtr client) : device_(std::move(device)), - permission_provider_(std::move(permission_provider)), observer_(this), + client_(std::move(client)), weak_factory_(this) { DCHECK(device_); observer_.Add(device_.get()); @@ -128,8 +127,8 @@ DeviceImpl::DeviceImpl(scoped_refptr<device::UsbDevice> device, void DeviceImpl::CloseHandle() { if (device_handle_) { device_handle_->Close(); - if (permission_provider_) - permission_provider_->DecrementConnectionCount(); + if (client_) + client_->OnDeviceClosed(); } device_handle_ = nullptr; } @@ -175,8 +174,9 @@ void DeviceImpl::OnOpen(base::WeakPtr<DeviceImpl> self, } self->device_handle_ = std::move(handle); - if (self->device_handle_ && self->permission_provider_) - self->permission_provider_->IncrementConnectionCount(); + if (self->device_handle_ && self->client_) + self->client_->OnDeviceOpened(); + std::move(callback).Run(self->device_handle_ ? mojom::UsbOpenDeviceError::OK : mojom::UsbOpenDeviceError::ACCESS_DENIED); diff --git a/chromium/device/usb/mojo/device_impl.h b/chromium/device/usb/mojo/device_impl.h index 4a82e065a58..74062646312 100644 --- a/chromium/device/usb/mojo/device_impl.h +++ b/chromium/device/usb/mojo/device_impl.h @@ -13,7 +13,6 @@ #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" #include "base/scoped_observer.h" -#include "device/usb/mojo/permission_provider.h" #include "device/usb/public/mojom/device.mojom.h" #include "device/usb/usb_device.h" #include "device/usb/usb_device_handle.h" @@ -22,22 +21,20 @@ namespace device { namespace usb { -class PermissionProvider; - // Implementation of the public Device interface. Instances of this class are // constructed by DeviceManagerImpl and are strongly bound to their MessagePipe // lifetime. class DeviceImpl : public mojom::UsbDevice, public device::UsbDevice::Observer { public: static void Create(scoped_refptr<device::UsbDevice> device, - base::WeakPtr<PermissionProvider> permission_provider, - mojom::UsbDeviceRequest request); + mojom::UsbDeviceRequest request, + mojom::UsbDeviceClientPtr client); ~DeviceImpl() override; private: DeviceImpl(scoped_refptr<device::UsbDevice> device, - base::WeakPtr<PermissionProvider> permission_provider); + mojom::UsbDeviceClientPtr client); // Closes the device if it's open. This will always set |device_handle_| to // null. @@ -99,7 +96,6 @@ class DeviceImpl : public mojom::UsbDevice, public device::UsbDevice::Observer { void OnDeviceRemoved(scoped_refptr<device::UsbDevice> device) override; const scoped_refptr<device::UsbDevice> device_; - base::WeakPtr<PermissionProvider> permission_provider_; ScopedObserver<device::UsbDevice, device::UsbDevice::Observer> observer_; // The device handle. Will be null before the device is opened and after it @@ -107,6 +103,7 @@ class DeviceImpl : public mojom::UsbDevice, public device::UsbDevice::Observer { scoped_refptr<UsbDeviceHandle> device_handle_; mojo::StrongBindingPtr<mojom::UsbDevice> binding_; + device::mojom::UsbDeviceClientPtr client_; base::WeakPtrFactory<DeviceImpl> weak_factory_; DISALLOW_COPY_AND_ASSIGN(DeviceImpl); diff --git a/chromium/device/usb/mojo/device_impl_unittest.cc b/chromium/device/usb/mojo/device_impl_unittest.cc index 0c707a06d0a..0dd2eed8ecb 100644 --- a/chromium/device/usb/mojo/device_impl_unittest.cc +++ b/chromium/device/usb/mojo/device_impl_unittest.cc @@ -24,7 +24,6 @@ #include "base/stl_util.h" #include "device/usb/mock_usb_device.h" #include "device/usb/mock_usb_device_handle.h" -#include "device/usb/mojo/mock_permission_provider.h" #include "device/usb/mojo/type_converters.h" #include "mojo/public/cpp/bindings/interface_request.h" #include "mojo/public/cpp/bindings/strong_binding.h" @@ -138,6 +137,24 @@ void ExpectTransferStatusAndThen(mojom::UsbTransferStatus expected_status, continuation.Run(); } +class MockUsbDeviceClient : public mojom::UsbDeviceClient { + public: + MockUsbDeviceClient() : binding_(this) {} + ~MockUsbDeviceClient() override = default; + + mojom::UsbDeviceClientPtr CreateInterfacePtrAndBind() { + mojom::UsbDeviceClientPtr client; + binding_.Bind(mojo::MakeRequest(&client)); + return client; + } + + MOCK_METHOD0(OnDeviceOpened, void()); + MOCK_METHOD0(OnDeviceClosed, void()); + + private: + mojo::Binding<mojom::UsbDeviceClient> binding_; +}; + class USBDeviceImplTest : public testing::Test { public: USBDeviceImplTest() @@ -150,7 +167,6 @@ class USBDeviceImplTest : public testing::Test { void TearDown() override { base::RunLoop().RunUntilIdle(); } protected: - MockPermissionProvider& permission_provider() { return permission_provider_; } MockUsbDevice& mock_device() { return *mock_device_.get(); } bool is_device_open() const { return is_device_open_; } MockUsbDeviceHandle& mock_handle() { return *mock_handle_.get(); } @@ -163,14 +179,15 @@ class USBDeviceImplTest : public testing::Test { uint16_t product_id, const std::string& manufacturer, const std::string& product, - const std::string& serial) { + const std::string& serial, + mojom::UsbDeviceClientPtr client) { mock_device_ = new MockUsbDevice(vendor_id, product_id, manufacturer, product, serial); mock_handle_ = new MockUsbDeviceHandle(mock_device_.get()); UsbDevicePtr proxy; - DeviceImpl::Create(mock_device_, permission_provider_.GetWeakPtr(), - mojo::MakeRequest(&proxy)); + DeviceImpl::Create(mock_device_, mojo::MakeRequest(&proxy), + std::move(client)); // Set up mock handle calls to respond based on mock device configs // established by the test. @@ -202,10 +219,13 @@ class USBDeviceImplTest : public testing::Test { return proxy; } - UsbDevicePtr GetMockDeviceProxy() { - return GetMockDeviceProxy(0x1234, 0x5678, "ACME", "Frobinator", "ABCDEF"); + UsbDevicePtr GetMockDeviceProxy(mojom::UsbDeviceClientPtr client) { + return GetMockDeviceProxy(0x1234, 0x5678, "ACME", "Frobinator", "ABCDEF", + std::move(client)); } + UsbDevicePtr GetMockDeviceProxy() { return GetMockDeviceProxy(nullptr); } + void AddMockConfig(const ConfigBuilder& builder) { const UsbConfigDescriptor& config = builder.config(); DCHECK(!base::ContainsKey(mock_configs_, config.configuration_value)); @@ -419,8 +439,6 @@ class USBDeviceImplTest : public testing::Test { std::set<uint8_t> claimed_interfaces_; - MockPermissionProvider permission_provider_; - DISALLOW_COPY_AND_ASSIGN(USBDeviceImplTest); }; @@ -436,12 +454,14 @@ TEST_F(USBDeviceImplTest, Disconnect) { } TEST_F(USBDeviceImplTest, Open) { - UsbDevicePtr device = GetMockDeviceProxy(); + MockUsbDeviceClient device_client; + UsbDevicePtr device = + GetMockDeviceProxy(device_client.CreateInterfacePtrAndBind()); EXPECT_FALSE(is_device_open()); EXPECT_CALL(mock_device(), OpenInternal(_)); - EXPECT_CALL(permission_provider(), IncrementConnectionCount()); + EXPECT_CALL(device_client, OnDeviceOpened()); { base::RunLoop loop; @@ -459,7 +479,10 @@ TEST_F(USBDeviceImplTest, Open) { } EXPECT_CALL(mock_handle(), Close()); - EXPECT_CALL(permission_provider(), DecrementConnectionCount()); + EXPECT_CALL(device_client, OnDeviceClosed()); + + device.reset(); + base::RunLoop().RunUntilIdle(); } TEST_F(USBDeviceImplTest, OpenFailure) { diff --git a/chromium/device/usb/mojo/device_manager_impl.cc b/chromium/device/usb/mojo/device_manager_impl.cc index 5f611f2ffdd..8240e533eb6 100644 --- a/chromium/device/usb/mojo/device_manager_impl.cc +++ b/chromium/device/usb/mojo/device_manager_impl.cc @@ -15,7 +15,6 @@ #include "base/memory/ptr_util.h" #include "device/base/device_client.h" #include "device/usb/mojo/device_impl.h" -#include "device/usb/mojo/permission_provider.h" #include "device/usb/mojo/type_converters.h" #include "device/usb/public/cpp/filter_utils.h" #include "device/usb/public/mojom/device.mojom.h" @@ -27,26 +26,19 @@ namespace usb { // static void DeviceManagerImpl::Create( - base::WeakPtr<PermissionProvider> permission_provider, mojom::UsbDeviceManagerRequest request) { DCHECK(DeviceClient::Get()); UsbService* service = DeviceClient::Get()->GetUsbService(); if (!service) return; - auto* device_manager_impl = - new DeviceManagerImpl(std::move(permission_provider), service); + auto* device_manager_impl = new DeviceManagerImpl(service); device_manager_impl->binding_ = mojo::MakeStrongBinding( base::WrapUnique(device_manager_impl), std::move(request)); } -DeviceManagerImpl::DeviceManagerImpl( - base::WeakPtr<PermissionProvider> permission_provider, - UsbService* usb_service) - : permission_provider_(permission_provider), - usb_service_(usb_service), - observer_(this), - weak_factory_(this) { +DeviceManagerImpl::DeviceManagerImpl(UsbService* usb_service) + : usb_service_(usb_service), observer_(this), weak_factory_(this) { // This object owns itself and will be destroyed if the message pipe it is // bound to is closed, the message loop is destructed, or the UsbService is // shut down. @@ -63,16 +55,14 @@ void DeviceManagerImpl::GetDevices(mojom::UsbEnumerationOptionsPtr options, } void DeviceManagerImpl::GetDevice(const std::string& guid, - mojom::UsbDeviceRequest device_request) { + mojom::UsbDeviceRequest device_request, + mojom::UsbDeviceClientPtr device_client) { scoped_refptr<UsbDevice> device = usb_service_->GetDevice(guid); if (!device) return; - if (permission_provider_ && - permission_provider_->HasDevicePermission(device)) { - DeviceImpl::Create(std::move(device), permission_provider_, - std::move(device_request)); - } + DeviceImpl::Create(std::move(device), std::move(device_request), + std::move(device_client)); } void DeviceManagerImpl::SetClient(mojom::UsbDeviceManagerClientPtr client) { @@ -90,10 +80,7 @@ void DeviceManagerImpl::OnGetDevices( std::vector<mojom::UsbDeviceInfoPtr> device_infos; for (const auto& device : devices) { if (UsbDeviceFilterMatchesAny(filters, *device)) { - if (permission_provider_ && - permission_provider_->HasDevicePermission(device)) { - device_infos.push_back(mojom::UsbDeviceInfo::From(*device)); - } + device_infos.push_back(mojom::UsbDeviceInfo::From(*device)); } } @@ -101,14 +88,12 @@ void DeviceManagerImpl::OnGetDevices( } void DeviceManagerImpl::OnDeviceAdded(scoped_refptr<UsbDevice> device) { - if (client_ && permission_provider_ && - permission_provider_->HasDevicePermission(device)) + if (client_) client_->OnDeviceAdded(mojom::UsbDeviceInfo::From(*device)); } void DeviceManagerImpl::OnDeviceRemoved(scoped_refptr<UsbDevice> device) { - if (client_ && permission_provider_ && - permission_provider_->HasDevicePermission(device)) + if (client_) client_->OnDeviceRemoved(mojom::UsbDeviceInfo::From(*device)); } diff --git a/chromium/device/usb/mojo/device_manager_impl.h b/chromium/device/usb/mojo/device_manager_impl.h index 095419dbb6d..43224dda71d 100644 --- a/chromium/device/usb/mojo/device_manager_impl.h +++ b/chromium/device/usb/mojo/device_manager_impl.h @@ -8,6 +8,8 @@ #include <memory> #include <queue> #include <set> +#include <string> +#include <vector> #include "base/callback.h" #include "base/macros.h" @@ -24,27 +26,24 @@ class UsbDevice; namespace usb { -class PermissionProvider; - // Implements the public Mojo UsbDeviceManager interface by wrapping the // UsbService instance. class DeviceManagerImpl : public mojom::UsbDeviceManager, public UsbService::Observer { public: - static void Create(base::WeakPtr<PermissionProvider> permission_provider, - mojom::UsbDeviceManagerRequest request); + static void Create(mojom::UsbDeviceManagerRequest request); ~DeviceManagerImpl() override; private: - DeviceManagerImpl(base::WeakPtr<PermissionProvider> permission_provider, - UsbService* usb_service); + explicit DeviceManagerImpl(UsbService* usb_service); // DeviceManager implementation: void GetDevices(mojom::UsbEnumerationOptionsPtr options, GetDevicesCallback callback) override; void GetDevice(const std::string& guid, - mojom::UsbDeviceRequest device_request) override; + mojom::UsbDeviceRequest device_request, + mojom::UsbDeviceClientPtr device_client) override; void SetClient(mojom::UsbDeviceManagerClientPtr client) override; // Callbacks to handle the async responses from the underlying UsbService. @@ -60,7 +59,6 @@ class DeviceManagerImpl : public mojom::UsbDeviceManager, void MaybeRunDeviceChangesCallback(); mojo::StrongBindingPtr<mojom::UsbDeviceManager> binding_; - base::WeakPtr<PermissionProvider> permission_provider_; UsbService* usb_service_; ScopedObserver<UsbService, UsbService::Observer> observer_; diff --git a/chromium/device/usb/mojo/device_manager_impl_unittest.cc b/chromium/device/usb/mojo/device_manager_impl_unittest.cc index ed3ed79e95e..d81db9d250b 100644 --- a/chromium/device/usb/mojo/device_manager_impl_unittest.cc +++ b/chromium/device/usb/mojo/device_manager_impl_unittest.cc @@ -22,7 +22,6 @@ #include "device/usb/mock_usb_device_handle.h" #include "device/usb/mock_usb_service.h" #include "device/usb/mojo/device_impl.h" -#include "device/usb/mojo/mock_permission_provider.h" #include "mojo/public/cpp/bindings/binding.h" #include "testing/gtest/include/gtest/gtest.h" @@ -56,15 +55,13 @@ class USBDeviceManagerImplTest : public testing::Test { protected: UsbDeviceManagerPtr ConnectToDeviceManager() { UsbDeviceManagerPtr device_manager; - DeviceManagerImpl::Create(permission_provider_.GetWeakPtr(), - mojo::MakeRequest(&device_manager)); + DeviceManagerImpl::Create(mojo::MakeRequest(&device_manager)); return device_manager; } MockDeviceClient device_client_; private: - MockPermissionProvider permission_provider_; std::unique_ptr<base::MessageLoop> message_loop_; }; @@ -152,7 +149,8 @@ TEST_F(USBDeviceManagerImplTest, GetDevice) { { base::RunLoop loop; UsbDevicePtr device; - device_manager->GetDevice(mock_device->guid(), mojo::MakeRequest(&device)); + device_manager->GetDevice(mock_device->guid(), mojo::MakeRequest(&device), + /*device_client=*/nullptr); // Close is a no-op if the device hasn't been opened but ensures that the // pipe was successfully connected. device->Close(loop.QuitClosure()); @@ -160,7 +158,8 @@ TEST_F(USBDeviceManagerImplTest, GetDevice) { } UsbDevicePtr bad_device; - device_manager->GetDevice("not a real guid", mojo::MakeRequest(&bad_device)); + device_manager->GetDevice("not a real guid", mojo::MakeRequest(&bad_device), + /*device_client=*/nullptr); { base::RunLoop loop; diff --git a/chromium/device/usb/mojo/mock_permission_provider.cc b/chromium/device/usb/mojo/mock_permission_provider.cc deleted file mode 100644 index 690688026a5..00000000000 --- a/chromium/device/usb/mojo/mock_permission_provider.cc +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2015 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 "device/usb/mojo/mock_permission_provider.h" - -#include <stddef.h> -#include <utility> - -#include "device/usb/public/mojom/device.mojom.h" - -using ::testing::Return; -using ::testing::_; - -namespace device { -namespace usb { - -MockPermissionProvider::MockPermissionProvider() : weak_factory_(this) { - ON_CALL(*this, HasDevicePermission(_)).WillByDefault(Return(true)); -} - -MockPermissionProvider::~MockPermissionProvider() = default; - -base::WeakPtr<PermissionProvider> MockPermissionProvider::GetWeakPtr() { - return weak_factory_.GetWeakPtr(); -} - -} // namespace usb -} // namespace device diff --git a/chromium/device/usb/mojo/mock_permission_provider.h b/chromium/device/usb/mojo/mock_permission_provider.h deleted file mode 100644 index c558323a271..00000000000 --- a/chromium/device/usb/mojo/mock_permission_provider.h +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2015 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. - -#ifndef DEVICE_USB_MOJO_MOCK_PERMISSION_PROVIDER_H_ -#define DEVICE_USB_MOJO_MOCK_PERMISSION_PROVIDER_H_ - -#include <stdint.h> - -#include "base/memory/weak_ptr.h" -#include "device/usb/mojo/permission_provider.h" -#include "device/usb/usb_device.h" -#include "testing/gmock/include/gmock/gmock.h" - -namespace device { -namespace usb { - -class MockPermissionProvider : public PermissionProvider { - public: - MockPermissionProvider(); - ~MockPermissionProvider() override; - - base::WeakPtr<PermissionProvider> GetWeakPtr(); - MOCK_CONST_METHOD1(HasDevicePermission, - bool(scoped_refptr<const UsbDevice> device)); - - MOCK_METHOD0(IncrementConnectionCount, void()); - MOCK_METHOD0(DecrementConnectionCount, void()); - - private: - base::WeakPtrFactory<PermissionProvider> weak_factory_; -}; - -} // namespace usb -} // namespace device - -#endif // DEVICE_USB_MOCK_MOJO_PERMISSION_PROVIDER_H_ diff --git a/chromium/device/usb/mojo/permission_provider.cc b/chromium/device/usb/mojo/permission_provider.cc deleted file mode 100644 index 806993de682..00000000000 --- a/chromium/device/usb/mojo/permission_provider.cc +++ /dev/null @@ -1,15 +0,0 @@ -// 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 "device/usb/mojo/permission_provider.h" - -namespace device { -namespace usb { - -PermissionProvider::PermissionProvider() = default; - -PermissionProvider::~PermissionProvider() = default; - -} // namespace usb -} // namespace device diff --git a/chromium/device/usb/mojo/permission_provider.h b/chromium/device/usb/mojo/permission_provider.h deleted file mode 100644 index 5bb6a716670..00000000000 --- a/chromium/device/usb/mojo/permission_provider.h +++ /dev/null @@ -1,32 +0,0 @@ -// 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. - -#ifndef DEVICE_USB_MOJO_PERMISSION_PROVIDER_H_ -#define DEVICE_USB_MOJO_PERMISSION_PROVIDER_H_ - -#include "base/memory/ref_counted.h" - -namespace device { - -class UsbDevice; - -namespace usb { - -// An implementation of this interface must be provided to a DeviceManager in -// order to implement device permission checks. -class PermissionProvider { - public: - PermissionProvider(); - virtual ~PermissionProvider(); - - virtual bool HasDevicePermission( - scoped_refptr<const UsbDevice> device) const = 0; - virtual void IncrementConnectionCount() = 0; - virtual void DecrementConnectionCount() = 0; -}; - -} // namespace usb -} // namespace device - -#endif // DEVICE_USB_MOJO_PERMISSION_PROVIDER_H_ diff --git a/chromium/device/usb/public/mojom/BUILD.gn b/chromium/device/usb/public/mojom/BUILD.gn index 1c496ed638a..703ff1e77d6 100644 --- a/chromium/device/usb/public/mojom/BUILD.gn +++ b/chromium/device/usb/public/mojom/BUILD.gn @@ -6,7 +6,6 @@ import("//mojo/public/tools/bindings/mojom.gni") mojom("mojom") { sources = [ - "chooser_service.mojom", "device.mojom", "device_manager.mojom", ] @@ -19,4 +18,18 @@ mojom("mojom") { # prepackaged redistributable JS bindings. It is therefore not desirable to # scramble these messages. scramble_message_ids = false + + # The blink variant of the usb mojom is depended on by the blink platform + # target. All blink variant mojoms use WTF types, which are part of the + # blink platform component. In order to avoid a dependency cycle, these + # targets must be part of that component. + export_class_attribute_blink = "BLINK_PLATFORM_EXPORT" + export_define_blink = "BLINK_PLATFORM_IMPLEMENTATION=1" + export_header_blink = "third_party/blink/public/platform/web_common.h" + + visibility_blink = [ + "//components/arc/common:common_blink", + "//third_party/blink/public/mojom/usb:usb_blink", + "//third_party/blink/renderer/modules/webusb", + ] } diff --git a/chromium/device/usb/public/mojom/chooser_service.mojom b/chromium/device/usb/public/mojom/chooser_service.mojom deleted file mode 100644 index 0b08daecbbf..00000000000 --- a/chromium/device/usb/public/mojom/chooser_service.mojom +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2015 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. - -module device.mojom; - -import "device/usb/public/mojom/device.mojom"; -import "device/usb/public/mojom/device_manager.mojom"; - -interface UsbChooserService { - // Get permission from user to use the device. - // - // |device_filters| filters the available devices and the filtered - // devices will be listed for user to grant permission. - // - // |result| is the device that user grants permission to use. - GetPermission(array<device.mojom.UsbDeviceFilter> device_filters) - => (device.mojom.UsbDeviceInfo? result); -}; diff --git a/chromium/device/usb/public/mojom/device.mojom b/chromium/device/usb/public/mojom/device.mojom index 4967853a0a0..17b2b498442 100644 --- a/chromium/device/usb/public/mojom/device.mojom +++ b/chromium/device/usb/public/mojom/device.mojom @@ -271,3 +271,19 @@ interface UsbDevice { uint32 timeout) => (array<UsbIsochronousPacket> packets); }; + +// This client is introduced for keeping connection count for a tab through +// WebUSBPermissionProvider. It will be implemented by WebUsbServiceImpl and +// passed to the UsbDevice via UsbDeviceManager::GetDevice method. +interface UsbDeviceClient { + // Called when a device is opened successfully. + // This event may be triggered once more than OnDeviceClosed while the device + // is in use. + OnDeviceOpened(); + + // Called when a device is closed successfully. + // This event may not be triggered when the device is in use. But eventually, + // after the whole lifecycle, it is expected to have the same number of this + // call as that of OnDeviceOpened. + OnDeviceClosed(); +}; diff --git a/chromium/device/usb/public/mojom/device_manager.mojom b/chromium/device/usb/public/mojom/device_manager.mojom index 2ef620e6c7e..9c783d498cb 100644 --- a/chromium/device/usb/public/mojom/device_manager.mojom +++ b/chromium/device/usb/public/mojom/device_manager.mojom @@ -36,7 +36,8 @@ interface UsbDeviceManager { GetDevices(UsbEnumerationOptions? options) => (array<UsbDeviceInfo> results); // Requests a device by guid. - GetDevice(string guid, UsbDevice& device_request); + GetDevice(string guid, UsbDevice& device_request, + UsbDeviceClient? device_client); // Sets the client for this DeviceManager service. The service will notify its // client of device events such as connection and disconnection. diff --git a/chromium/device/usb/scoped_libusb_device_handle.cc b/chromium/device/usb/scoped_libusb_device_handle.cc new file mode 100644 index 00000000000..4223a8136b7 --- /dev/null +++ b/chromium/device/usb/scoped_libusb_device_handle.cc @@ -0,0 +1,37 @@ +// Copyright 2018 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 "device/usb/scoped_libusb_device_handle.h" + +#include "device/usb/usb_context.h" +#include "third_party/libusb/src/libusb/libusb.h" + +namespace device { + +ScopedLibusbDeviceHandle::ScopedLibusbDeviceHandle( + libusb_device_handle* handle, + scoped_refptr<UsbContext> context) + : handle_(handle), context_(std::move(context)) {} + +ScopedLibusbDeviceHandle::ScopedLibusbDeviceHandle( + ScopedLibusbDeviceHandle&& other) + : handle_(other.handle_), context_(std::move(other.context_)) { + other.handle_ = nullptr; +} + +ScopedLibusbDeviceHandle::~ScopedLibusbDeviceHandle() { + Reset(); +} + +void ScopedLibusbDeviceHandle::Reset() { + libusb_close(handle_); + handle_ = nullptr; + context_.reset(); +} + +bool ScopedLibusbDeviceHandle::IsValid() const { + return handle_ != nullptr; +} + +} // namespace device diff --git a/chromium/device/usb/scoped_libusb_device_handle.h b/chromium/device/usb/scoped_libusb_device_handle.h new file mode 100644 index 00000000000..a4eb378b3d7 --- /dev/null +++ b/chromium/device/usb/scoped_libusb_device_handle.h @@ -0,0 +1,41 @@ +// Copyright 2018 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. + +#ifndef DEVICE_USB_SCOPED_LIBUSB_DEVICE_HANDLE_H_ +#define DEVICE_USB_SCOPED_LIBUSB_DEVICE_HANDLE_H_ + +#include "base/macros.h" +#include "base/memory/scoped_refptr.h" + +struct libusb_device_handle; + +namespace device { + +class UsbContext; + +// This class owns a reference to a libusb_device_handle as well as a reference +// to the libusb_context. The libusb_context must outlive any +// libusb_device_handle instances created from it. +class ScopedLibusbDeviceHandle { + public: + ScopedLibusbDeviceHandle(libusb_device_handle* handle, + scoped_refptr<UsbContext> context); + ScopedLibusbDeviceHandle(ScopedLibusbDeviceHandle&& other); + ~ScopedLibusbDeviceHandle(); + + libusb_device_handle* get() const { return handle_; } + + void Reset(); + bool IsValid() const; + + private: + libusb_device_handle* handle_; + scoped_refptr<UsbContext> context_; + + DISALLOW_COPY_AND_ASSIGN(ScopedLibusbDeviceHandle); +}; + +} // namespace device + +#endif // DEVICE_USB_SCOPED_LIBUSB_DEVICE_HANDLE_H_ diff --git a/chromium/device/usb/scoped_libusb_device_ref.cc b/chromium/device/usb/scoped_libusb_device_ref.cc new file mode 100644 index 00000000000..b3ac5e8eb08 --- /dev/null +++ b/chromium/device/usb/scoped_libusb_device_ref.cc @@ -0,0 +1,39 @@ +// Copyright 2018 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 "device/usb/scoped_libusb_device_ref.h" + +#include "device/usb/usb_context.h" +#include "third_party/libusb/src/libusb/libusb.h" + +namespace device { + +ScopedLibusbDeviceRef::ScopedLibusbDeviceRef(libusb_device* device, + scoped_refptr<UsbContext> context) + : device_(device), context_(std::move(context)) {} + +ScopedLibusbDeviceRef::ScopedLibusbDeviceRef(ScopedLibusbDeviceRef&& other) + : device_(other.device_), context_(std::move(other.context_)) { + other.device_ = nullptr; +} + +ScopedLibusbDeviceRef::~ScopedLibusbDeviceRef() { + Reset(); +} + +void ScopedLibusbDeviceRef::Reset() { + libusb_unref_device(device_); + device_ = nullptr; + context_.reset(); +} + +bool ScopedLibusbDeviceRef::IsValid() const { + return device_ != nullptr; +} + +bool operator==(const ScopedLibusbDeviceRef& ref, libusb_device* device) { + return ref.get() == device; +} + +} // namespace device diff --git a/chromium/device/usb/scoped_libusb_device_ref.h b/chromium/device/usb/scoped_libusb_device_ref.h index 05a21b237b4..73a37310895 100644 --- a/chromium/device/usb/scoped_libusb_device_ref.h +++ b/chromium/device/usb/scoped_libusb_device_ref.h @@ -5,19 +5,40 @@ #ifndef DEVICE_USB_SCOPED_LIBUSB_DEVICE_REF_H_ #define DEVICE_USB_SCOPED_LIBUSB_DEVICE_REF_H_ -#include "base/scoped_generic.h" -#include "third_party/libusb/src/libusb/libusb.h" +#include "base/macros.h" +#include "base/memory/scoped_refptr.h" + +struct libusb_device; namespace device { -struct LibusbDeviceRefTraits { - static libusb_device* InvalidValue() { return nullptr; } +class UsbContext; + +// This class owns a reference to a libusb_device as well as a reference to +// the libusb_context. The libusb_context must outlive any libusb_device +// instances created from it. +class ScopedLibusbDeviceRef { + public: + ScopedLibusbDeviceRef(libusb_device* device, + scoped_refptr<UsbContext> context); + ScopedLibusbDeviceRef(ScopedLibusbDeviceRef&& other); + ~ScopedLibusbDeviceRef(); + + libusb_device* get() const { return device_; } + + scoped_refptr<UsbContext> GetContext() const { return context_; } + + void Reset(); + bool IsValid() const; + + private: + libusb_device* device_; + scoped_refptr<UsbContext> context_; - static void Free(libusb_device* device) { libusb_unref_device(device); } + DISALLOW_COPY_AND_ASSIGN(ScopedLibusbDeviceRef); }; -using ScopedLibusbDeviceRef = - base::ScopedGeneric<libusb_device*, LibusbDeviceRefTraits>; +bool operator==(const ScopedLibusbDeviceRef& ref, libusb_device* device); } // namespace device diff --git a/chromium/device/usb/usb_device.h b/chromium/device/usb/usb_device.h index e6427af4212..b9cdc68046f 100644 --- a/chromium/device/usb/usb_device.h +++ b/chromium/device/usb/usb_device.h @@ -147,7 +147,7 @@ class UsbDevice : public base::RefCountedThreadSafe<UsbDevice> { // is freed. std::list<UsbDeviceHandle*> handles_; - base::ObserverList<Observer, true> observer_list_; + base::ObserverList<Observer, true>::Unchecked observer_list_; DISALLOW_COPY_AND_ASSIGN(UsbDevice); }; diff --git a/chromium/device/usb/usb_device_handle_impl.cc b/chromium/device/usb/usb_device_handle_impl.cc index cce0691d14d..b8c2307a690 100644 --- a/chromium/device/usb/usb_device_handle_impl.cc +++ b/chromium/device/usb/usb_device_handle_impl.cc @@ -311,7 +311,7 @@ UsbDeviceHandleImpl::Transfer::CreateControlTransfer( libusb_fill_control_setup(buffer->front(), type, request, value, index, length); libusb_fill_control_transfer(transfer->platform_transfer_, - device_handle->handle_, buffer->front(), + device_handle->handle(), buffer->front(), &UsbDeviceHandleImpl::Transfer::PlatformCallback, transfer.get(), timeout); @@ -341,7 +341,7 @@ UsbDeviceHandleImpl::Transfer::CreateBulkTransfer( } libusb_fill_bulk_transfer( - transfer->platform_transfer_, device_handle->handle_, endpoint, + transfer->platform_transfer_, device_handle->handle(), endpoint, buffer->front(), length, &UsbDeviceHandleImpl::Transfer::PlatformCallback, transfer.get(), timeout); @@ -371,7 +371,7 @@ UsbDeviceHandleImpl::Transfer::CreateInterruptTransfer( } libusb_fill_interrupt_transfer( - transfer->platform_transfer_, device_handle->handle_, endpoint, + transfer->platform_transfer_, device_handle->handle(), endpoint, buffer->front(), length, &UsbDeviceHandleImpl::Transfer::PlatformCallback, transfer.get(), timeout); @@ -401,10 +401,10 @@ UsbDeviceHandleImpl::Transfer::CreateIsochronousTransfer( return nullptr; } - libusb_fill_iso_transfer(transfer->platform_transfer_, device_handle->handle_, - endpoint, buffer->front(), static_cast<int>(length), - num_packets, &Transfer::PlatformCallback, - transfer.get(), timeout); + libusb_fill_iso_transfer( + transfer->platform_transfer_, device_handle->handle(), endpoint, + buffer->front(), static_cast<int>(length), num_packets, + &Transfer::PlatformCallback, transfer.get(), timeout); for (size_t i = 0; i < packet_lengths.size(); ++i) transfer->platform_transfer_->iso_packet_desc[i].length = packet_lengths[i]; @@ -798,16 +798,14 @@ const UsbInterfaceDescriptor* UsbDeviceHandleImpl::FindInterfaceByEndpoint( } UsbDeviceHandleImpl::UsbDeviceHandleImpl( - scoped_refptr<UsbContext> context, scoped_refptr<UsbDeviceImpl> device, - PlatformUsbDeviceHandle handle, + ScopedLibusbDeviceHandle handle, scoped_refptr<base::SequencedTaskRunner> blocking_task_runner) - : device_(device), - handle_(handle), - context_(context), + : device_(std::move(device)), + handle_(std::move(handle)), task_runner_(base::ThreadTaskRunnerHandle::Get()), blocking_task_runner_(blocking_task_runner) { - DCHECK(handle) << "Cannot create device with NULL handle."; + DCHECK(handle_.IsValid()) << "Cannot create device with an invalid handle."; } UsbDeviceHandleImpl::~UsbDeviceHandleImpl() { @@ -817,17 +815,19 @@ UsbDeviceHandleImpl::~UsbDeviceHandleImpl() { // any thread. libusb is not safe to reentrancy so be sure not to try to close // the device from inside a transfer completion callback. if (blocking_task_runner_->RunsTasksInCurrentSequence()) { - libusb_close(handle_); + handle_.Reset(); } else { - blocking_task_runner_->PostTask(FROM_HERE, - base::BindOnce(&libusb_close, handle_)); + blocking_task_runner_->PostTask( + FROM_HERE, + base::BindOnce(base::DoNothing::Once<ScopedLibusbDeviceHandle>(), + std::move(handle_))); } } void UsbDeviceHandleImpl::SetConfigurationOnBlockingThread( int configuration_value, ResultCallback callback) { - int rv = libusb_set_configuration(handle_, configuration_value); + int rv = libusb_set_configuration(handle(), configuration_value); if (rv != LIBUSB_SUCCESS) { USB_LOG(EVENT) << "Failed to set configuration " << configuration_value << ": " << ConvertPlatformUsbErrorToString(rv); @@ -855,7 +855,7 @@ void UsbDeviceHandleImpl::SetConfigurationComplete(bool success, void UsbDeviceHandleImpl::ClaimInterfaceOnBlockingThread( int interface_number, ResultCallback callback) { - int rv = libusb_claim_interface(handle_, interface_number); + int rv = libusb_claim_interface(handle(), interface_number); scoped_refptr<InterfaceClaimer> interface_claimer; if (rv == LIBUSB_SUCCESS) { interface_claimer = @@ -897,7 +897,7 @@ void UsbDeviceHandleImpl::SetInterfaceAlternateSettingOnBlockingThread( int interface_number, int alternate_setting, ResultCallback callback) { - int rv = libusb_set_interface_alt_setting(handle_, interface_number, + int rv = libusb_set_interface_alt_setting(handle(), interface_number, alternate_setting); if (rv != LIBUSB_SUCCESS) { USB_LOG(EVENT) << "Failed to set interface " << interface_number @@ -930,7 +930,7 @@ void UsbDeviceHandleImpl::SetInterfaceAlternateSettingComplete( } void UsbDeviceHandleImpl::ResetDeviceOnBlockingThread(ResultCallback callback) { - int rv = libusb_reset_device(handle_); + int rv = libusb_reset_device(handle()); if (rv != LIBUSB_SUCCESS) { USB_LOG(EVENT) << "Failed to reset device: " << ConvertPlatformUsbErrorToString(rv); @@ -941,7 +941,7 @@ void UsbDeviceHandleImpl::ResetDeviceOnBlockingThread(ResultCallback callback) { void UsbDeviceHandleImpl::ClearHaltOnBlockingThread(uint8_t endpoint, ResultCallback callback) { - int rv = libusb_clear_halt(handle_, endpoint); + int rv = libusb_clear_halt(handle(), endpoint); if (rv != LIBUSB_SUCCESS) { USB_LOG(EVENT) << "Failed to clear halt: " << ConvertPlatformUsbErrorToString(rv); diff --git a/chromium/device/usb/usb_device_handle_impl.h b/chromium/device/usb/usb_device_handle_impl.h index acf0332d11e..32dbe04a375 100644 --- a/chromium/device/usb/usb_device_handle_impl.h +++ b/chromium/device/usb/usb_device_handle_impl.h @@ -17,6 +17,7 @@ #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/threading/thread_checker.h" +#include "device/usb/scoped_libusb_device_handle.h" #include "device/usb/usb_device_handle.h" #include "third_party/libusb/src/libusb/libusb.h" @@ -34,10 +35,8 @@ struct EndpointMapValue { const UsbEndpointDescriptor* endpoint; }; -class UsbContext; class UsbDeviceImpl; -typedef libusb_device_handle* PlatformUsbDeviceHandle; typedef libusb_iso_packet_descriptor* PlatformUsbIsoPacketDescriptor; typedef libusb_transfer* PlatformUsbTransferHandle; @@ -90,14 +89,13 @@ class UsbDeviceHandleImpl : public UsbDeviceHandle { // This constructor is called by UsbDeviceImpl. UsbDeviceHandleImpl( - scoped_refptr<UsbContext> context, scoped_refptr<UsbDeviceImpl> device, - PlatformUsbDeviceHandle handle, + ScopedLibusbDeviceHandle handle, scoped_refptr<base::SequencedTaskRunner> blocking_task_runner); ~UsbDeviceHandleImpl() override; - PlatformUsbDeviceHandle handle() const { return handle_; } + libusb_device_handle* handle() const { return handle_.get(); } private: class InterfaceClaimer; @@ -174,7 +172,7 @@ class UsbDeviceHandleImpl : public UsbDeviceHandle { scoped_refptr<UsbDeviceImpl> device_; - PlatformUsbDeviceHandle handle_; + ScopedLibusbDeviceHandle handle_; typedef std::map<int, scoped_refptr<InterfaceClaimer>> ClaimedInterfaceMap; ClaimedInterfaceMap claimed_interfaces_; @@ -186,10 +184,6 @@ class UsbDeviceHandleImpl : public UsbDeviceHandle { typedef std::map<int, EndpointMapValue> EndpointMap; EndpointMap endpoint_map_; - // Retain the UsbContext so that the platform context will not be destroyed - // before this handle. - scoped_refptr<UsbContext> context_; - scoped_refptr<base::SingleThreadTaskRunner> task_runner_; scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_; diff --git a/chromium/device/usb/usb_device_impl.cc b/chromium/device/usb/usb_device_impl.cc index 056c6c34f64..ab734eabe15 100644 --- a/chromium/device/usb/usb_device_impl.cc +++ b/chromium/device/usb/usb_device_impl.cc @@ -28,8 +28,7 @@ namespace device { -UsbDeviceImpl::UsbDeviceImpl(scoped_refptr<UsbContext> context, - ScopedLibusbDeviceRef platform_device, +UsbDeviceImpl::UsbDeviceImpl(ScopedLibusbDeviceRef platform_device, const libusb_device_descriptor& descriptor) : UsbDevice(descriptor.bcdUSB, descriptor.bDeviceClass, @@ -41,9 +40,8 @@ UsbDeviceImpl::UsbDeviceImpl(scoped_refptr<UsbContext> context, base::string16(), base::string16(), base::string16()), - context_(std::move(context)), platform_device_(std::move(platform_device)) { - CHECK(platform_device_.is_valid()) << "platform_device must be valid"; + CHECK(platform_device_.IsValid()) << "platform_device must be valid"; ReadAllConfigurations(); RefreshActiveConfiguration(); } @@ -104,12 +102,15 @@ void UsbDeviceImpl::OpenOnBlockingThread( scoped_refptr<base::TaskRunner> task_runner, scoped_refptr<base::SequencedTaskRunner> blocking_task_runner) { base::AssertBlockingAllowed(); - PlatformUsbDeviceHandle handle; + libusb_device_handle* handle = nullptr; const int rv = libusb_open(platform_device(), &handle); if (LIBUSB_SUCCESS == rv) { + ScopedLibusbDeviceHandle scoped_handle(handle, + platform_device_.GetContext()); task_runner->PostTask( - FROM_HERE, base::BindOnce(&UsbDeviceImpl::Opened, this, handle, - std::move(callback), blocking_task_runner)); + FROM_HERE, + base::BindOnce(&UsbDeviceImpl::Opened, this, std::move(scoped_handle), + std::move(callback), blocking_task_runner)); } else { USB_LOG(EVENT) << "Failed to open device: " << ConvertPlatformUsbErrorToString(rv); @@ -119,12 +120,12 @@ void UsbDeviceImpl::OpenOnBlockingThread( } void UsbDeviceImpl::Opened( - PlatformUsbDeviceHandle platform_handle, + ScopedLibusbDeviceHandle platform_handle, OpenCallback callback, scoped_refptr<base::SequencedTaskRunner> blocking_task_runner) { DCHECK(thread_checker_.CalledOnValidThread()); scoped_refptr<UsbDeviceHandle> device_handle = new UsbDeviceHandleImpl( - context_, this, platform_handle, blocking_task_runner); + this, std::move(platform_handle), blocking_task_runner); handles().push_back(device_handle.get()); std::move(callback).Run(device_handle); } diff --git a/chromium/device/usb/usb_device_impl.h b/chromium/device/usb/usb_device_impl.h index 16ab891600e..9e3d2c7db91 100644 --- a/chromium/device/usb/usb_device_impl.h +++ b/chromium/device/usb/usb_device_impl.h @@ -21,9 +21,7 @@ #include "device/usb/usb_descriptors.h" #include "device/usb/usb_device.h" -struct libusb_device; struct libusb_device_descriptor; -struct libusb_device_handle; namespace base { class SequencedTaskRunner; @@ -31,15 +29,12 @@ class SequencedTaskRunner; namespace device { +class ScopedLibusbDeviceHandle; class UsbDeviceHandleImpl; -class UsbContext; - -typedef struct libusb_device_handle* PlatformUsbDeviceHandle; class UsbDeviceImpl : public UsbDevice { public: - UsbDeviceImpl(scoped_refptr<UsbContext> context, - ScopedLibusbDeviceRef platform_device, + UsbDeviceImpl(ScopedLibusbDeviceRef platform_device, const libusb_device_descriptor& descriptor); // UsbDevice implementation: @@ -79,15 +74,13 @@ class UsbDeviceImpl : public UsbDevice { OpenCallback callback, scoped_refptr<base::TaskRunner> task_runner, scoped_refptr<base::SequencedTaskRunner> blocking_task_runner); - void Opened(PlatformUsbDeviceHandle platform_handle, + void Opened(ScopedLibusbDeviceHandle platform_handle, OpenCallback callback, scoped_refptr<base::SequencedTaskRunner> blocking_task_runner); base::ThreadChecker thread_checker_; bool visited_ = false; - // The libusb_context must not be released before the libusb_device. - const scoped_refptr<UsbContext> context_; const ScopedLibusbDeviceRef platform_device_; DISALLOW_COPY_AND_ASSIGN(UsbDeviceImpl); diff --git a/chromium/device/usb/usb_service.cc b/chromium/device/usb/usb_service.cc index 457b0e1145a..bfea0c0bd73 100644 --- a/chromium/device/usb/usb_service.cc +++ b/chromium/device/usb/usb_service.cc @@ -8,7 +8,7 @@ #include "base/feature_list.h" #include "base/location.h" #include "base/memory/ptr_util.h" -#include "base/task_scheduler/post_task.h" +#include "base/task/post_task.h" #include "base/threading/thread_task_runner_handle.h" #include "build/build_config.h" #include "components/device_event_log/device_event_log.h" diff --git a/chromium/device/usb/usb_service.h b/chromium/device/usb/usb_service.h index c4728cc768a..8e613436b59 100644 --- a/chromium/device/usb/usb_service.h +++ b/chromium/device/usb/usb_service.h @@ -19,7 +19,7 @@ #include "base/sequence_checker.h" #include "base/sequenced_task_runner.h" #include "base/single_thread_task_runner.h" -#include "base/task_scheduler/task_traits.h" +#include "base/task/task_traits.h" namespace device { @@ -103,7 +103,7 @@ class UsbService { scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_; std::unordered_map<std::string, scoped_refptr<UsbDevice>> devices_; std::unordered_set<std::string> testing_devices_; - base::ObserverList<Observer, true> observer_list_; + base::ObserverList<Observer, true>::Unchecked observer_list_; DISALLOW_COPY_AND_ASSIGN(UsbService); }; diff --git a/chromium/device/usb/usb_service_impl.cc b/chromium/device/usb/usb_service_impl.cc index 83e14c6f0c1..6b7a8036a35 100644 --- a/chromium/device/usb/usb_service_impl.cc +++ b/chromium/device/usb/usb_service_impl.cc @@ -21,7 +21,7 @@ #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" -#include "base/task_scheduler/post_task.h" +#include "base/task/post_task.h" #include "build/build_config.h" #include "components/device_event_log/device_event_log.h" #include "device/usb/usb_device_handle.h" @@ -131,7 +131,7 @@ void GetDeviceListOnBlockingThread( std::vector<ScopedLibusbDeviceRef> scoped_devices; scoped_devices.reserve(device_count); for (ssize_t i = 0; i < device_count; ++i) - scoped_devices.emplace_back(platform_devices[i]); + scoped_devices.emplace_back(platform_devices[i], usb_context); // Free the list but don't unref the devices because ownership has been // been transfered to the elements of |scoped_devices|. @@ -231,6 +231,7 @@ UsbServiceImpl::UsbServiceImpl() device_observer_(this), #endif weak_factory_(this) { + weak_self_ = weak_factory_.GetWeakPtr(); base::PostTaskWithTraits( FROM_HERE, kBlockingTaskTraits, base::Bind(&InitializeUsbContextOnBlockingThread, task_runner(), @@ -361,7 +362,7 @@ void UsbServiceImpl::OnDeviceList( // Mark the existing device object visited and remove it from the list so // it will not be ignored. it->second->set_visited(true); - device.reset(); + device.Reset(); } } @@ -382,7 +383,7 @@ void UsbServiceImpl::OnDeviceList( // that have been removed don't remain in |ignored_devices_| indefinitely. ignored_devices_.clear(); for (auto& device : *devices) { - if (device.is_valid()) + if (device.IsValid()) ignored_devices_.push_back(std::move(device)); } @@ -441,8 +442,8 @@ void UsbServiceImpl::EnumerateDevice(ScopedLibusbDeviceRef platform_device, devices_being_enumerated_.insert(platform_device.get()); - auto device = base::MakeRefCounted<UsbDeviceImpl>( - context_, std::move(platform_device), descriptor); + auto device = base::MakeRefCounted<UsbDeviceImpl>(std::move(platform_device), + descriptor); base::OnceClosure add_device = base::BindOnce(&UsbServiceImpl::AddDevice, weak_factory_.GetWeakPtr(), refresh_complete, device); @@ -458,7 +459,8 @@ void UsbServiceImpl::EnumerateDevice(ScopedLibusbDeviceRef platform_device, libusb_ref_device(device->platform_device()); base::OnceClosure enumeration_failed = base::BindOnce( &UsbServiceImpl::EnumerationFailed, weak_factory_.GetWeakPtr(), - ScopedLibusbDeviceRef(device->platform_device()), refresh_complete); + ScopedLibusbDeviceRef(device->platform_device(), context_), + refresh_complete); device->Open(base::BindOnce( &OnDeviceOpenedReadDescriptors, descriptor.iManufacturer, @@ -518,18 +520,18 @@ int LIBUSB_CALL UsbServiceImpl::HotplugCallback(libusb_context* context, // libusb does not transfer ownership of |device_raw| to this function so a // reference must be taken here. libusb_ref_device(device_raw); - ScopedLibusbDeviceRef device(device_raw); + ScopedLibusbDeviceRef device(device_raw, self->context_); switch (event) { case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED: self->task_runner()->PostTask( FROM_HERE, base::BindOnce(&UsbServiceImpl::OnPlatformDeviceAdded, - base::Unretained(self), std::move(device))); + self->weak_self_, std::move(device))); break; case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT: self->task_runner()->PostTask( FROM_HERE, base::BindOnce(&UsbServiceImpl::OnPlatformDeviceRemoved, - base::Unretained(self), std::move(device))); + self->weak_self_, std::move(device))); break; default: NOTREACHED(); diff --git a/chromium/device/usb/usb_service_impl.h b/chromium/device/usb/usb_service_impl.h index e7e89d0f545..36f71154f9c 100644 --- a/chromium/device/usb/usb_service_impl.h +++ b/chromium/device/usb/usb_service_impl.h @@ -122,6 +122,10 @@ class UsbServiceImpl : ScopedObserver<DeviceMonitorWin, DeviceMonitorWin::Observer> device_observer_; #endif // OS_WIN + // This WeakPtr is used to safely post hotplug events back to the thread this + // object lives on. + base::WeakPtr<UsbServiceImpl> weak_self_; + base::WeakPtrFactory<UsbServiceImpl> weak_factory_; DISALLOW_COPY_AND_ASSIGN(UsbServiceImpl); diff --git a/chromium/device/vr/BUILD.gn b/chromium/device/vr/BUILD.gn index f0a7cb1fe1f..1f2e6a4b585 100644 --- a/chromium/device/vr/BUILD.gn +++ b/chromium/device/vr/BUILD.gn @@ -96,8 +96,8 @@ if (enable_vr) { "openvr/openvr_device.h", "openvr/openvr_device_provider.cc", "openvr/openvr_device_provider.h", - "openvr/openvr_gamepad_data_fetcher.cc", - "openvr/openvr_gamepad_data_fetcher.h", + "openvr/openvr_gamepad_helper.cc", + "openvr/openvr_gamepad_helper.h", "openvr/openvr_render_loop.cc", "openvr/openvr_render_loop.h", "openvr/openvr_type_converters.cc", @@ -113,6 +113,8 @@ if (enable_vr) { ] sources += [ + "isolated_gamepad_data_fetcher.cc", + "isolated_gamepad_data_fetcher.h", "windows/d3d11_texture_helper.cc", "windows/d3d11_texture_helper.h", "windows/flip_pixel_shader.h", @@ -132,8 +134,8 @@ if (enable_vr) { "oculus/oculus_device.h", "oculus/oculus_device_provider.cc", "oculus/oculus_device_provider.h", - "oculus/oculus_gamepad_data_fetcher.cc", - "oculus/oculus_gamepad_data_fetcher.h", + "oculus/oculus_gamepad_helper.cc", + "oculus/oculus_gamepad_helper.h", "oculus/oculus_render_loop.cc", "oculus/oculus_render_loop.h", "oculus/oculus_type_converters.cc", @@ -160,8 +162,6 @@ if (enable_vr) { "test/fake_vr_display_impl_client.h", "test/fake_vr_service_client.cc", "test/fake_vr_service_client.h", - "test/mock_vr_display_impl.cc", - "test/mock_vr_display_impl.h", "vr_export.h", ] diff --git a/chromium/device/vr/android/gvr/cardboard_gamepad_data_fetcher.cc b/chromium/device/vr/android/gvr/cardboard_gamepad_data_fetcher.cc index 03e7d72dece..b151a27ef58 100644 --- a/chromium/device/vr/android/gvr/cardboard_gamepad_data_fetcher.cc +++ b/chromium/device/vr/android/gvr/cardboard_gamepad_data_fetcher.cc @@ -26,7 +26,7 @@ void CopyToUString(UChar* dest, size_t dest_length, base::string16 src) { CardboardGamepadDataFetcher::Factory::Factory( CardboardGamepadDataProvider* data_provider, - unsigned int display_id) + device::mojom::XRDeviceId display_id) : data_provider_(data_provider), display_id_(display_id) { DVLOG(1) << __FUNCTION__ << "=" << this; } @@ -47,7 +47,7 @@ GamepadSource CardboardGamepadDataFetcher::Factory::source() { CardboardGamepadDataFetcher::CardboardGamepadDataFetcher( CardboardGamepadDataProvider* data_provider, - unsigned int display_id) + device::mojom::XRDeviceId display_id) : display_id_(display_id) { // Called on UI thread. DVLOG(1) << __FUNCTION__ << "=" << this; @@ -93,7 +93,7 @@ void CardboardGamepadDataFetcher::GetGamepadData(bool devices_changed_hint) { pad.buttons_length = 1; pad.axes_length = 0; - pad.display_id = display_id_; + pad.display_id = static_cast<unsigned int>(display_id_); pad.hand = GamepadHand::kNone; } diff --git a/chromium/device/vr/android/gvr/cardboard_gamepad_data_fetcher.h b/chromium/device/vr/android/gvr/cardboard_gamepad_data_fetcher.h index 0b6193faf8d..0fe4651849f 100644 --- a/chromium/device/vr/android/gvr/cardboard_gamepad_data_fetcher.h +++ b/chromium/device/vr/android/gvr/cardboard_gamepad_data_fetcher.h @@ -9,6 +9,7 @@ #include "device/gamepad/gamepad_data_fetcher.h" #include "device/vr/android/gvr/cardboard_gamepad_data_provider.h" +#include "device/vr/public/mojom/vr_service.mojom.h" #include "device/vr/vr_export.h" namespace device { @@ -17,18 +18,19 @@ class DEVICE_VR_EXPORT CardboardGamepadDataFetcher : public GamepadDataFetcher { public: class Factory : public GamepadDataFetcherFactory { public: - Factory(CardboardGamepadDataProvider*, unsigned int display_id); + Factory(CardboardGamepadDataProvider*, + device::mojom::XRDeviceId display_id); ~Factory() override; std::unique_ptr<GamepadDataFetcher> CreateDataFetcher() override; GamepadSource source() override; private: CardboardGamepadDataProvider* data_provider_; - unsigned int display_id_; + device::mojom::XRDeviceId display_id_; }; CardboardGamepadDataFetcher(CardboardGamepadDataProvider*, - unsigned int display_id); + device::mojom::XRDeviceId display_id); ~CardboardGamepadDataFetcher() override; GamepadSource source() override; @@ -41,7 +43,7 @@ class DEVICE_VR_EXPORT CardboardGamepadDataFetcher : public GamepadDataFetcher { void SetGamepadData(CardboardGamepadData); private: - unsigned int display_id_; + device::mojom::XRDeviceId display_id_; CardboardGamepadData gamepad_data_; DISALLOW_COPY_AND_ASSIGN(CardboardGamepadDataFetcher); diff --git a/chromium/device/vr/android/gvr/gvr_delegate_provider.h b/chromium/device/vr/android/gvr/gvr_delegate_provider.h index 9bd7708c62b..60c3419e363 100644 --- a/chromium/device/vr/android/gvr/gvr_delegate_provider.h +++ b/chromium/device/vr/android/gvr/gvr_delegate_provider.h @@ -19,10 +19,10 @@ class DEVICE_VR_EXPORT GvrDelegateProvider { public: GvrDelegateProvider() = default; virtual bool ShouldDisableGvrDevice() = 0; - virtual void SetDeviceId(unsigned int device_id) = 0; + virtual void SetDeviceId(mojom::XRDeviceId device_id) = 0; virtual void StartWebXRPresentation( mojom::VRDisplayInfoPtr display_info, - mojom::XRDeviceRuntimeSessionOptionsPtr options, + mojom::XRRuntimeSessionOptionsPtr options, base::OnceCallback<void(device::mojom::XRSessionPtr)> callback) = 0; virtual void ExitWebVRPresent() = 0; virtual void OnListeningForActivateChanged(bool listening) = 0; diff --git a/chromium/device/vr/android/gvr/gvr_device.cc b/chromium/device/vr/android/gvr/gvr_device.cc index 20166c658f6..c47946f2093 100644 --- a/chromium/device/vr/android/gvr/gvr_device.cc +++ b/chromium/device/vr/android/gvr/gvr_device.cc @@ -93,12 +93,12 @@ mojom::VREyeParametersPtr CreateEyeParamater( } mojom::VRDisplayInfoPtr CreateVRDisplayInfo(gvr::GvrApi* gvr_api, - uint32_t device_id) { + mojom::XRDeviceId device_id) { TRACE_EVENT0("input", "GvrDelegate::CreateVRDisplayInfo"); mojom::VRDisplayInfoPtr device = mojom::VRDisplayInfo::New(); - device->index = device_id; + device->id = device_id; device->capabilities = mojom::VRDisplayCapabilities::New(); device->capabilities->hasPosition = false; @@ -143,7 +143,7 @@ std::unique_ptr<GvrDevice> GvrDevice::Create() { } GvrDevice::GvrDevice() - : VRDeviceBase(VRDeviceId::GVR_DEVICE_ID), + : VRDeviceBase(mojom::XRDeviceId::GVR_DEVICE_ID), exclusive_controller_binding_(this), weak_ptr_factory_(this) { GvrDelegateProvider* delegate_provider = GetGvrDelegateProvider(); @@ -177,34 +177,33 @@ GvrDevice::~GvrDevice() { } void GvrDevice::RequestSession( - mojom::XRDeviceRuntimeSessionOptionsPtr options, + mojom::XRRuntimeSessionOptionsPtr options, mojom::XRRuntime::RequestSessionCallback callback) { + if (!options->immersive) { + // TODO(https://crbug.com/695937): This should be NOTREACHED() once we no + // longer need the hacked GRV non-immersive mode. + ReturnNonImmersiveSession(std::move(callback)); + return; + } + GvrDelegateProvider* delegate_provider = GetGvrDelegateProvider(); if (!delegate_provider) { std::move(callback).Run(nullptr, nullptr); return; } - if (options->immersive) { - // StartWebXRPresentation is async as we may trigger a DON (Device ON) flow - // that pauses Chrome. - delegate_provider->StartWebXRPresentation( - GetVRDisplayInfo(), std::move(options), - base::BindOnce(&GvrDevice::OnStartPresentResult, - weak_ptr_factory_.GetWeakPtr(), std::move(callback))); - } else { - // TODO(https://crbug.com/695937): This should be NOTREACHED() once - // orientation device handles non-immersive VR sessions. - // TODO(https://crbug.com/842025): Handle this when RequestSession is called - // for non-immersive sessions. - NOTREACHED(); - } + // StartWebXRPresentation is async as we may trigger a DON (Device ON) flow + // that pauses Chrome. + delegate_provider->StartWebXRPresentation( + GetVRDisplayInfo(), std::move(options), + base::BindOnce(&GvrDevice::OnStartPresentResult, + weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } void GvrDevice::OnStartPresentResult( mojom::XRRuntime::RequestSessionCallback callback, mojom::XRSessionPtr session) { - if (!session || !session->connection) { + if (!session) { std::move(callback).Run(nullptr, nullptr); return; } @@ -223,8 +222,7 @@ void GvrDevice::OnStartPresentResult( base::BindOnce(&GvrDevice::OnPresentingControllerMojoConnectionError, base::Unretained(this))); - std::move(callback).Run(std::move(session->connection), - std::move(session_controller)); + std::move(callback).Run(std::move(session), std::move(session_controller)); } // XRSessionController @@ -246,7 +244,7 @@ void GvrDevice::StopPresenting() { } void GvrDevice::OnMagicWindowFrameDataRequest( - mojom::VRMagicWindowProvider::GetFrameDataCallback callback) { + mojom::XRFrameDataProvider::GetFrameDataCallback callback) { mojom::XRFrameDataPtr frame_data = mojom::XRFrameData::New(); frame_data->pose = GvrDelegate::GetVRPosePtrWithNeckModel(gvr_api_.get(), nullptr); diff --git a/chromium/device/vr/android/gvr/gvr_device.h b/chromium/device/vr/android/gvr/gvr_device.h index 6061fcbed79..1d802b230c1 100644 --- a/chromium/device/vr/android/gvr/gvr_device.h +++ b/chromium/device/vr/android/gvr/gvr_device.h @@ -26,7 +26,7 @@ class DEVICE_VR_EXPORT GvrDevice : public VRDeviceBase, // VRDeviceBase void RequestSession( - mojom::XRDeviceRuntimeSessionOptionsPtr options, + mojom::XRRuntimeSessionOptionsPtr options, mojom::XRRuntime::RequestSessionCallback callback) override; void PauseTracking() override; void ResumeTracking() override; @@ -42,7 +42,7 @@ class DEVICE_VR_EXPORT GvrDevice : public VRDeviceBase, // VRDeviceBase void OnListeningForActivate(bool listening) override; void OnMagicWindowFrameDataRequest( - mojom::VRMagicWindowProvider::GetFrameDataCallback callback) override; + mojom::XRFrameDataProvider::GetFrameDataCallback callback) override; void OnStartPresentResult(mojom::XRRuntime::RequestSessionCallback callback, mojom::XRSessionPtr session); diff --git a/chromium/device/vr/android/gvr/gvr_device_provider.cc b/chromium/device/vr/android/gvr/gvr_device_provider.cc index ea77025b1e8..56f8e792a57 100644 --- a/chromium/device/vr/android/gvr/gvr_device_provider.cc +++ b/chromium/device/vr/android/gvr/gvr_device_provider.cc @@ -12,10 +12,10 @@ GvrDeviceProvider::GvrDeviceProvider() = default; GvrDeviceProvider::~GvrDeviceProvider() = default; void GvrDeviceProvider::Initialize( - base::RepeatingCallback<void(unsigned int, + base::RepeatingCallback<void(mojom::XRDeviceId, mojom::VRDisplayInfoPtr, mojom::XRRuntimePtr)> add_device_callback, - base::RepeatingCallback<void(unsigned int)> remove_device_callback, + base::RepeatingCallback<void(mojom::XRDeviceId)> remove_device_callback, base::OnceClosure initialization_complete) { vr_device_ = GvrDevice::Create(); if (vr_device_) diff --git a/chromium/device/vr/android/gvr/gvr_device_provider.h b/chromium/device/vr/android/gvr/gvr_device_provider.h index c833e473404..527b84715cb 100644 --- a/chromium/device/vr/android/gvr/gvr_device_provider.h +++ b/chromium/device/vr/android/gvr/gvr_device_provider.h @@ -21,10 +21,10 @@ class DEVICE_VR_EXPORT GvrDeviceProvider : public VRDeviceProvider { ~GvrDeviceProvider() override; void Initialize( - base::RepeatingCallback<void(unsigned int, + base::RepeatingCallback<void(mojom::XRDeviceId, mojom::VRDisplayInfoPtr, mojom::XRRuntimePtr)> add_device_callback, - base::RepeatingCallback<void(unsigned int)> remove_device_callback, + base::RepeatingCallback<void(mojom::XRDeviceId)> remove_device_callback, base::OnceClosure initialization_complete) override; bool Initialized() override; diff --git a/chromium/device/vr/android/gvr/gvr_gamepad_data_fetcher.cc b/chromium/device/vr/android/gvr/gvr_gamepad_data_fetcher.cc index f48fcd31133..0d50dd8cbe2 100644 --- a/chromium/device/vr/android/gvr/gvr_gamepad_data_fetcher.cc +++ b/chromium/device/vr/android/gvr/gvr_gamepad_data_fetcher.cc @@ -26,7 +26,7 @@ void CopyToUString(UChar* dest, size_t dest_length, base::string16 src) { } // namespace GvrGamepadDataFetcher::Factory::Factory(GvrGamepadDataProvider* data_provider, - unsigned int display_id) + mojom::XRDeviceId display_id) : data_provider_(data_provider), display_id_(display_id) { DVLOG(1) << __FUNCTION__ << "=" << this; } @@ -46,7 +46,7 @@ GamepadSource GvrGamepadDataFetcher::Factory::source() { GvrGamepadDataFetcher::GvrGamepadDataFetcher( GvrGamepadDataProvider* data_provider, - unsigned int display_id) + mojom::XRDeviceId display_id) : display_id_(display_id) { // Called on UI thread. DVLOG(1) << __FUNCTION__ << "=" << this; @@ -93,7 +93,7 @@ void GvrGamepadDataFetcher::GetGamepadData(bool devices_changed_hint) { pad.buttons_length = 1; pad.axes_length = 2; - pad.display_id = display_id_; + pad.display_id = static_cast<unsigned int>(display_id_); pad.is_xr = true; diff --git a/chromium/device/vr/android/gvr/gvr_gamepad_data_fetcher.h b/chromium/device/vr/android/gvr/gvr_gamepad_data_fetcher.h index 816046e51b9..e4a0da53e22 100644 --- a/chromium/device/vr/android/gvr/gvr_gamepad_data_fetcher.h +++ b/chromium/device/vr/android/gvr/gvr_gamepad_data_fetcher.h @@ -9,6 +9,7 @@ #include "device/gamepad/gamepad_data_fetcher.h" #include "device/vr/android/gvr/gvr_gamepad_data_provider.h" +#include "device/vr/public/mojom/vr_service.mojom.h" #include "device/vr/vr_export.h" namespace device { @@ -17,17 +18,17 @@ class DEVICE_VR_EXPORT GvrGamepadDataFetcher : public GamepadDataFetcher { public: class Factory : public GamepadDataFetcherFactory { public: - Factory(GvrGamepadDataProvider*, unsigned int display_id); + Factory(GvrGamepadDataProvider*, mojom::XRDeviceId display_id); ~Factory() override; std::unique_ptr<GamepadDataFetcher> CreateDataFetcher() override; GamepadSource source() override; private: GvrGamepadDataProvider* data_provider_; - unsigned int display_id_; + mojom::XRDeviceId display_id_; }; - GvrGamepadDataFetcher(GvrGamepadDataProvider*, unsigned int display_id); + GvrGamepadDataFetcher(GvrGamepadDataProvider*, mojom::XRDeviceId display_id); ~GvrGamepadDataFetcher() override; GamepadSource source() override; @@ -40,7 +41,7 @@ class DEVICE_VR_EXPORT GvrGamepadDataFetcher : public GamepadDataFetcher { void SetGamepadData(GvrGamepadData); private: - unsigned int display_id_; + mojom::XRDeviceId display_id_; GvrGamepadData gamepad_data_; DISALLOW_COPY_AND_ASSIGN(GvrGamepadDataFetcher); diff --git a/chromium/device/vr/buildflags/BUILD.gn b/chromium/device/vr/buildflags/BUILD.gn index fa4c3d95ca4..2bbefac3108 100644 --- a/chromium/device/vr/buildflags/BUILD.gn +++ b/chromium/device/vr/buildflags/BUILD.gn @@ -10,8 +10,9 @@ buildflag_header("buildflags") { header = "buildflags.h" flags = [ "ENABLE_ARCORE=$enable_arcore", - "ENABLE_VR=$enable_vr", - "ENABLE_OPENVR=$enable_openvr", + "ENABLE_ISOLATED_XR_SERVICE=$enable_isolated_xr_service", "ENABLE_OCULUS_VR=$enable_oculus_vr", + "ENABLE_OPENVR=$enable_openvr", + "ENABLE_VR=$enable_vr", ] } diff --git a/chromium/device/vr/buildflags/buildflags.gni b/chromium/device/vr/buildflags/buildflags.gni index 7e663a3e315..13f05de973b 100644 --- a/chromium/device/vr/buildflags/buildflags.gni +++ b/chromium/device/vr/buildflags/buildflags.gni @@ -37,10 +37,10 @@ declare_args() { # we are limiting to canary and dev until binary size issues are resolved. # TODO(crbug.com/836524): once we've refactored AR code out from vr # directories, we can stop requiring |enable_vr| here. - package_arcore = - enable_vr && is_android && !is_chromecast && current_cpu == "arm" && - (android_channel == "default" || android_channel == "canary" || - android_channel == "dev") + package_arcore = enable_vr && is_android && !is_chromecast && + (current_cpu == "arm" || current_cpu == "arm64") && + (android_channel == "default" || + android_channel == "canary" || android_channel == "dev") # TODO(crbug.com/841389): We should eventually have a single flag for # enabling arcore, but we currently don't support ARCore in 64bit, and we do @@ -50,4 +50,11 @@ declare_args() { # directories, we can stop requiring |enable_vr| here. enable_arcore = enable_vr && is_android && !is_chromecast && (current_cpu == "arm" || current_cpu == "arm64") + + # When enabled, host Desktop VR devices (OpenVR and Oculus) in a separate + # process. When disabled, and Oculus/OpenVR are enabled, they are hosted + # in the browser process. + # TODO(billorr): Enable by default when OpenVR or Oculus are enabled after + # tests and controllers are working with the isolated process. + enable_isolated_xr_service = false } diff --git a/chromium/device/vr/isolated_gamepad_data_fetcher.cc b/chromium/device/vr/isolated_gamepad_data_fetcher.cc new file mode 100644 index 00000000000..b78f5d5aeb0 --- /dev/null +++ b/chromium/device/vr/isolated_gamepad_data_fetcher.cc @@ -0,0 +1,225 @@ +// Copyright 2018 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 "device/vr/isolated_gamepad_data_fetcher.h" +#include "device/vr/vr_device.h" + +namespace device { + +IsolatedGamepadDataFetcher::Factory::Factory( + device::mojom::XRDeviceId display_id, + device::mojom::IsolatedXRGamepadProviderFactoryPtr factory) + : display_id_(display_id), factory_(std::move(factory)) {} + +IsolatedGamepadDataFetcher::Factory::~Factory() {} + +std::unique_ptr<GamepadDataFetcher> +IsolatedGamepadDataFetcher::Factory::CreateDataFetcher() { + device::mojom::IsolatedXRGamepadProviderPtr provider; + factory_->GetIsolatedXRGamepadProvider(mojo::MakeRequest(&provider)); + return std::make_unique<IsolatedGamepadDataFetcher>(display_id_, + std::move(provider)); +} + +GamepadSource IsolatedGamepadDataFetcher::Factory::source() { + return (display_id_ == device::mojom::XRDeviceId::OPENVR_DEVICE_ID) + ? GAMEPAD_SOURCE_OPENVR + : GAMEPAD_SOURCE_OCULUS; +} + +IsolatedGamepadDataFetcher::IsolatedGamepadDataFetcher( + device::mojom::XRDeviceId display_id, + device::mojom::IsolatedXRGamepadProviderPtr provider) + : display_id_(display_id) { + // We bind provider_ on the poling thread, but we're created on the main UI + // thread. + provider_info_ = provider.PassInterface(); +} + +IsolatedGamepadDataFetcher::~IsolatedGamepadDataFetcher() = default; + +GamepadSource IsolatedGamepadDataFetcher::source() { + return (display_id_ == device::mojom::XRDeviceId::OPENVR_DEVICE_ID) + ? GAMEPAD_SOURCE_OPENVR + : GAMEPAD_SOURCE_OCULUS; +} + +void IsolatedGamepadDataFetcher::OnDataUpdated( + device::mojom::XRGamepadDataPtr data) { + data_ = std::move(data); + have_outstanding_request_ = false; +} + +GamepadPose GamepadPoseFromXRPose(device::mojom::VRPose* pose) { + GamepadPose ret = {}; + ret.not_null = pose; + if (!pose) { + return ret; + } + + if (pose->position) { + ret.position.not_null = true; + ret.position.x = (*pose->position)[0]; + ret.position.y = (*pose->position)[1]; + ret.position.z = (*pose->position)[2]; + } + + if (pose->orientation) { + ret.orientation.not_null = true; + ret.orientation.x = (*pose->orientation)[0]; + ret.orientation.y = (*pose->orientation)[1]; + ret.orientation.z = (*pose->orientation)[2]; + ret.orientation.w = (*pose->orientation)[3]; + } + + if (pose->angularVelocity) { + ret.angular_velocity.not_null = true; + ret.angular_velocity.x = (*pose->angularVelocity)[0]; + ret.angular_velocity.y = (*pose->angularVelocity)[1]; + ret.angular_velocity.z = (*pose->angularVelocity)[2]; + } + + if (pose->linearVelocity) { + ret.linear_velocity.not_null = true; + ret.linear_velocity.x = (*pose->linearVelocity)[0]; + ret.linear_velocity.y = (*pose->linearVelocity)[1]; + ret.linear_velocity.z = (*pose->linearVelocity)[2]; + } + + if (pose->angularAcceleration) { + ret.angular_acceleration.not_null = true; + ret.angular_acceleration.x = (*pose->angularAcceleration)[0]; + ret.angular_acceleration.y = (*pose->angularAcceleration)[1]; + ret.angular_acceleration.z = (*pose->angularAcceleration)[2]; + } + + if (pose->linearAcceleration) { + ret.linear_acceleration.not_null = true; + ret.linear_acceleration.x = (*pose->linearAcceleration)[0]; + ret.linear_acceleration.y = (*pose->linearAcceleration)[1]; + ret.linear_acceleration.z = (*pose->linearAcceleration)[2]; + } + + return ret; +} + +void IsolatedGamepadDataFetcher::GetGamepadData(bool devices_changed_hint) { + if (!provider_ && provider_info_) { + provider_.Bind(std::move(provider_info_)); + } + + // If we don't have a provider, we can't give out data. + if (!provider_) { + return; + } + + // Request new data. + if (!have_outstanding_request_) { + have_outstanding_request_ = true; + provider_->RequestUpdate(base::BindOnce( + &IsolatedGamepadDataFetcher::OnDataUpdated, base::Unretained(this))); + } + + // If we have no data to give out, nothing to do. + if (!data_) { + return; + } + + // Keep track of gamepads that go missing. + std::set<unsigned int> seen_gamepads; + + // Give out data_, but we need to translate it. + for (size_t i = 0; i < data_->gamepads.size(); ++i) { + auto& source = data_->gamepads[i]; + PadState* state = GetPadState(source->controller_id); + if (!state) + continue; + Gamepad& dest = state->data; + dest.connected = true; + + seen_gamepads.insert(source->controller_id); + dest.timestamp = CurrentTimeInMicroseconds(); + dest.pose = GamepadPoseFromXRPose(source->pose.get()); + dest.pose.has_position = source->can_provide_position; + dest.pose.has_orientation = source->can_provide_orientation; + dest.hand = source->hand == device::mojom::XRHandedness::LEFT + ? GamepadHand::kLeft + : source->hand == device::mojom::XRHandedness::RIGHT + ? GamepadHand::kRight + : GamepadHand::kNone; + dest.display_id = static_cast<unsigned int>(display_id_); + dest.is_xr = true; + + dest.axes_length = source->axes.size(); + for (size_t j = 0; j < source->axes.size(); ++j) { + dest.axes[j] = source->axes[j]; + } + + dest.buttons_length = source->buttons.size(); + for (size_t j = 0; j < source->buttons.size(); ++j) { + dest.buttons[j] = + GamepadButton(source->buttons[j]->pressed, + source->buttons[j]->touched, source->buttons[j]->value); + } + + // Gamepad extensions refer to display_id, corresponding to the WebVR + // VRDisplay's dipslayId property. + // As WebVR is deprecated, and we only hand out a maximum of one "display" + // per runtime, we use the XRDeviceId now to associate the controller with + // a headset. This doesn't change behavior, but the device/display naming + // could be confusing here. + if (display_id_ == device::mojom::XRDeviceId::OPENVR_DEVICE_ID) { + swprintf(dest.id, Gamepad::kIdLengthCap, L"OpenVR Gamepad"); + } else { + if (dest.hand == GamepadHand::kLeft) { + swprintf(dest.id, Gamepad::kIdLengthCap, L"Oculus Touch (Left)"); + } else if (dest.hand == GamepadHand::kRight) { + swprintf(dest.id, Gamepad::kIdLengthCap, L"Oculus Touch (Right)"); + } else { + swprintf(dest.id, Gamepad::kIdLengthCap, L"Oculus Remote"); + } + } + + dest.mapping[0] = 0; + } + + // Remove any gamepads that aren't connected. + for (auto id : active_gamepads_) { + if (seen_gamepads.find(id) == seen_gamepads.end()) { + PadState* state = GetPadState(id); + if (state) { + state->data.connected = false; + } + } + } + active_gamepads_ = std::move(seen_gamepads); +} + +void IsolatedGamepadDataFetcher::PauseHint(bool paused) {} + +void IsolatedGamepadDataFetcher::OnAddedToProvider() {} + +void IsolatedGamepadDataFetcher::Factory::RemoveGamepad( + device::mojom::XRDeviceId id) { + using namespace device; + // TODO(crbug.com/868101) - Remove this. + std::map<device::mojom::XRDeviceId, GamepadSource> + device_id_to_gamepad_source = { + {device::mojom::XRDeviceId::OCULUS_DEVICE_ID, GAMEPAD_SOURCE_OCULUS}, + {device::mojom::XRDeviceId::OPENVR_DEVICE_ID, GAMEPAD_SOURCE_OPENVR}, + }; + + GamepadDataFetcherManager::GetInstance()->RemoveSourceFactory( + device_id_to_gamepad_source[id]); +} + +void IsolatedGamepadDataFetcher::Factory::AddGamepad( + device::mojom::XRDeviceId device_id, + device::mojom::IsolatedXRGamepadProviderFactoryPtr gamepad_factory) { + device::GamepadDataFetcherManager::GetInstance()->AddFactory( + new device::IsolatedGamepadDataFetcher::Factory( + device_id, std::move(gamepad_factory))); +} + +} // namespace device diff --git a/chromium/device/vr/isolated_gamepad_data_fetcher.h b/chromium/device/vr/isolated_gamepad_data_fetcher.h new file mode 100644 index 00000000000..39511405498 --- /dev/null +++ b/chromium/device/vr/isolated_gamepad_data_fetcher.h @@ -0,0 +1,61 @@ +// Copyright 2018 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. + +#ifndef DEVICE_VR_ISOLATED_GAMEPAD_DATA_FETCHER_H_ +#define DEVICE_VR_ISOLATED_GAMEPAD_DATA_FETCHER_H_ + +#include "device/gamepad/gamepad_data_fetcher.h" +#include "device/vr/public/mojom/isolated_xr_service.mojom.h" +#include "device/vr/vr_device.h" + +namespace device { + +class IsolatedGamepadDataFetcher : public GamepadDataFetcher { + public: + class DEVICE_VR_EXPORT Factory : public GamepadDataFetcherFactory { + public: + Factory(device::mojom::XRDeviceId display_id, + device::mojom::IsolatedXRGamepadProviderFactoryPtr factory); + ~Factory() override; + std::unique_ptr<GamepadDataFetcher> CreateDataFetcher() override; + GamepadSource source() override; + + static void AddGamepad( + device::mojom::XRDeviceId device_id, + device::mojom::IsolatedXRGamepadProviderFactoryPtr gamepad_factory); + static void RemoveGamepad(device::mojom::XRDeviceId device_id); + + private: + device::mojom::XRDeviceId display_id_; + device::mojom::IsolatedXRGamepadProviderFactoryPtr factory_; + }; + + IsolatedGamepadDataFetcher( + device::mojom::XRDeviceId display_id, + device::mojom::IsolatedXRGamepadProviderPtr provider); + ~IsolatedGamepadDataFetcher() override; + + GamepadSource source() override; + + void GetGamepadData(bool devices_changed_hint) override; + void PauseHint(bool paused) override; + void OnAddedToProvider() override; + + void OnDataUpdated(device::mojom::XRGamepadDataPtr data); + + private: + device::mojom::XRDeviceId display_id_; + bool have_outstanding_request_ = false; + std::set<unsigned int> active_gamepads_; + device::mojom::XRGamepadDataPtr data_; + device::mojom::IsolatedXRGamepadProviderPtr + provider_; // Bound on the polling thread. + device::mojom::IsolatedXRGamepadProviderPtrInfo + provider_info_; // Received on the UI thread, bound when polled. + + DISALLOW_COPY_AND_ASSIGN(IsolatedGamepadDataFetcher); +}; + +} // namespace device +#endif // DEVICE_VR_ISOLATED_GAMEPAD_DATA_FETCHER_H_ diff --git a/chromium/device/vr/oculus/oculus_device.cc b/chromium/device/vr/oculus/oculus_device.cc index f3d816d1e94..6f55e6669d3 100644 --- a/chromium/device/vr/oculus/oculus_device.cc +++ b/chromium/device/vr/oculus/oculus_device.cc @@ -48,10 +48,10 @@ mojom::VREyeParametersPtr GetEyeDetails(ovrSession session, return eye_parameters; } -mojom::VRDisplayInfoPtr CreateVRDisplayInfo(unsigned int id, +mojom::VRDisplayInfoPtr CreateVRDisplayInfo(mojom::XRDeviceId id, ovrSession session) { mojom::VRDisplayInfoPtr display_info = mojom::VRDisplayInfo::New(); - display_info->index = id; + display_info->id = id; display_info->displayName = std::string("Oculus"); display_info->capabilities = mojom::VRDisplayCapabilities::New(); display_info->capabilities->hasPosition = true; @@ -84,9 +84,10 @@ mojom::VRDisplayInfoPtr CreateVRDisplayInfo(unsigned int id, } // namespace OculusDevice::OculusDevice() - : VRDeviceBase(VRDeviceId::OCULUS_DEVICE_ID), + : VRDeviceBase(mojom::XRDeviceId::OCULUS_DEVICE_ID), main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()), exclusive_controller_binding_(this), + gamepad_provider_factory_binding_(this), weak_ptr_factory_(this) { StartOvrSession(); if (!session_) { @@ -97,36 +98,51 @@ OculusDevice::OculusDevice() render_loop_ = std::make_unique<OculusRenderLoop>( base::BindRepeating(&OculusDevice::OnPresentationEnded, - weak_ptr_factory_.GetWeakPtr()), - base::BindRepeating(&OculusDevice::OnControllerUpdated, weak_ptr_factory_.GetWeakPtr())); } -OculusDevice::~OculusDevice() { - StopOvrSession(); +mojom::IsolatedXRGamepadProviderFactoryPtr OculusDevice::BindGamepadFactory() { + mojom::IsolatedXRGamepadProviderFactoryPtr ret; + gamepad_provider_factory_binding_.Bind(mojo::MakeRequest(&ret)); + return ret; +} +OculusDevice::~OculusDevice() { // Wait for the render loop to stop before completing destruction. This will // ensure that bindings are closed on the correct thread. if (render_loop_ && render_loop_->IsRunning()) render_loop_->Stop(); - device::GamepadDataFetcherManager::GetInstance()->RemoveSourceFactory( - device::GAMEPAD_SOURCE_OCULUS); - data_fetcher_ = nullptr; + StopOvrSession(); } void OculusDevice::RequestSession( - mojom::XRDeviceRuntimeSessionOptionsPtr options, + mojom::XRRuntimeSessionOptionsPtr options, mojom::XRRuntime::RequestSessionCallback callback) { + if (!options->immersive) { + ReturnNonImmersiveSession(std::move(callback)); + return; + } + StopOvrSession(); - if (!render_loop_->IsRunning()) + if (!render_loop_->IsRunning()) { render_loop_->Start(); - if (!render_loop_->IsRunning()) { - std::move(callback).Run(nullptr, nullptr); - StartOvrSession(); - return; + if (!render_loop_->IsRunning()) { + std::move(callback).Run(nullptr, nullptr); + StartOvrSession(); + return; + } + + // If we have a pending gamepad provider request when starting the render + // loop, post the request over to the render loop to be bound. + if (provider_request_) { + render_loop_->task_runner()->PostTask( + FROM_HERE, base::BindOnce(&OculusRenderLoop::RequestGamepadProvider, + render_loop_->GetWeakPtr(), + std::move(provider_request_))); + } } auto on_request_present_result = @@ -142,9 +158,7 @@ void OculusDevice::RequestSession( void OculusDevice::OnRequestSessionResult( mojom::XRRuntime::RequestSessionCallback callback, bool result, - mojom::VRSubmitFrameClientRequest request, - mojom::VRPresentationProviderPtrInfo provider_info, - mojom::VRDisplayFrameTransportOptionsPtr transport_options) { + mojom::XRSessionPtr session) { if (!result) { std::move(callback).Run(nullptr, nullptr); @@ -155,11 +169,6 @@ void OculusDevice::OnRequestSessionResult( OnStartPresenting(); - auto connection = mojom::XRPresentationConnection::New(); - connection->client_request = std::move(request); - connection->provider = std::move(provider_info); - connection->transport_options = std::move(transport_options); - mojom::XRSessionControllerPtr session_controller; exclusive_controller_binding_.Bind(mojo::MakeRequest(&session_controller)); @@ -169,24 +178,9 @@ void OculusDevice::OnRequestSessionResult( base::BindOnce(&OculusDevice::OnPresentingControllerMojoConnectionError, base::Unretained(this))); - std::move(callback).Run(std::move(connection), std::move(session_controller)); + session->display_info = display_info_.Clone(); - if (!oculus_gamepad_factory_) { - oculus_gamepad_factory_ = - new OculusGamepadDataFetcher::Factory(GetId(), this); - GamepadDataFetcherManager::GetInstance()->AddFactory( - oculus_gamepad_factory_); - } -} - -void OculusDevice::OnControllerUpdated(ovrInputState input, - ovrInputState remote, - ovrTrackingState tracking, - bool has_touch, - bool has_remote) { - if (data_fetcher_) - data_fetcher_->UpdateGamepadData( - {input, remote, tracking, has_touch, has_remote}); + std::move(callback).Run(std::move(session), std::move(session_controller)); } // XRSessionController @@ -232,7 +226,7 @@ void OculusDevice::StopOvrSession() { } void OculusDevice::OnMagicWindowFrameDataRequest( - mojom::VRMagicWindowProvider::GetFrameDataCallback callback) { + mojom::XRFrameDataProvider::GetFrameDataCallback callback) { if (!session_) { std::move(callback).Run(nullptr); return; @@ -245,8 +239,20 @@ void OculusDevice::OnMagicWindowFrameDataRequest( std::move(callback).Run(std::move(frame_data)); } -void OculusDevice::RegisterDataFetcher(OculusGamepadDataFetcher* data_fetcher) { - data_fetcher_ = data_fetcher; +void OculusDevice::GetIsolatedXRGamepadProvider( + mojom::IsolatedXRGamepadProviderRequest provider_request) { + // We bind the provider_request on the render loop thread, so gamepad data is + // updated at the rendering rate. + // If we haven't started the render loop yet, postpone binding the request + // until we do. + if (render_loop_->IsRunning()) { + render_loop_->task_runner()->PostTask( + FROM_HERE, base::BindOnce(&OculusRenderLoop::RequestGamepadProvider, + render_loop_->GetWeakPtr(), + std::move(provider_request))); + } else { + provider_request_ = std::move(provider_request); + } } } // namespace device diff --git a/chromium/device/vr/oculus/oculus_device.h b/chromium/device/vr/oculus/oculus_device.h index cff84ccdc6a..6449d9e5f53 100644 --- a/chromium/device/vr/oculus/oculus_device.h +++ b/chromium/device/vr/oculus/oculus_device.h @@ -9,7 +9,6 @@ #include "base/macros.h" #include "base/single_thread_task_runner.h" -#include "device/vr/oculus/oculus_gamepad_data_fetcher.h" #include "device/vr/public/mojom/vr_service.mojom.h" #include "device/vr/vr_device_base.h" #include "mojo/public/cpp/bindings/binding.h" @@ -19,55 +18,50 @@ namespace device { class OculusRenderLoop; -class OculusDevice : public VRDeviceBase, - public mojom::XRSessionController, - public OculusGamepadDataProvider { +class DEVICE_VR_EXPORT OculusDevice + : public VRDeviceBase, + public mojom::XRSessionController, + public mojom::IsolatedXRGamepadProviderFactory { public: explicit OculusDevice(); ~OculusDevice() override; // VRDeviceBase void RequestSession( - mojom::XRDeviceRuntimeSessionOptionsPtr options, + mojom::XRRuntimeSessionOptionsPtr options, mojom::XRRuntime::RequestSessionCallback callback) override; void OnMagicWindowFrameDataRequest( - mojom::VRMagicWindowProvider::GetFrameDataCallback callback) override; - void OnRequestSessionResult( - mojom::XRRuntime::RequestSessionCallback callback, - bool result, - mojom::VRSubmitFrameClientRequest request, - mojom::VRPresentationProviderPtrInfo provider_info, - mojom::VRDisplayFrameTransportOptionsPtr transport_options); + mojom::XRFrameDataProvider::GetFrameDataCallback callback) override; + void OnRequestSessionResult(mojom::XRRuntime::RequestSessionCallback callback, + bool result, + mojom::XRSessionPtr session); bool IsInitialized() { return !!session_; } + mojom::IsolatedXRGamepadProviderFactoryPtr BindGamepadFactory(); + private: // XRSessionController void SetFrameDataRestricted(bool restricted) override; void OnPresentingControllerMojoConnectionError(); - // OculusGamepadDataProvider - void RegisterDataFetcher(OculusGamepadDataFetcher*) override; + // mojom::IsolatedXRGamepadProviderFactory + void GetIsolatedXRGamepadProvider( + mojom::IsolatedXRGamepadProviderRequest provider_request) override; void OnPresentationEnded(); void StartOvrSession(); void StopOvrSession(); - void OnControllerUpdated(ovrInputState input, - ovrInputState remote, - ovrTrackingState tracking, - bool has_touch, - bool has_remote); - std::unique_ptr<OculusRenderLoop> render_loop_; - OculusGamepadDataFetcher* data_fetcher_ = nullptr; - mojom::VRDisplayInfoPtr display_info_; ovrSession session_ = nullptr; - OculusGamepadDataFetcher::Factory* oculus_gamepad_factory_ = nullptr; scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_; mojo::Binding<mojom::XRSessionController> exclusive_controller_binding_; + mojo::Binding<mojom::IsolatedXRGamepadProviderFactory> + gamepad_provider_factory_binding_; + mojom::IsolatedXRGamepadProviderRequest provider_request_; base::WeakPtrFactory<OculusDevice> weak_ptr_factory_; diff --git a/chromium/device/vr/oculus/oculus_device_provider.cc b/chromium/device/vr/oculus/oculus_device_provider.cc index cda8a0a898f..b91253c247a 100644 --- a/chromium/device/vr/oculus/oculus_device_provider.cc +++ b/chromium/device/vr/oculus/oculus_device_provider.cc @@ -5,8 +5,8 @@ #include "device/vr/oculus/oculus_device_provider.h" #include "device/gamepad/gamepad_data_fetcher_manager.h" +#include "device/vr/isolated_gamepad_data_fetcher.h" #include "device/vr/oculus/oculus_device.h" -#include "device/vr/oculus/oculus_gamepad_data_fetcher.h" #include "third_party/libovr/src/Include/OVR_CAPI.h" namespace device { @@ -14,18 +14,32 @@ namespace device { OculusVRDeviceProvider::OculusVRDeviceProvider() : initialized_(false) {} OculusVRDeviceProvider::~OculusVRDeviceProvider() { + // Removing gamepad factory corresponding to VRDeviceId::OCULUS_DEVICE_ID, + // added during initialization. + device::GamepadDataFetcherManager::GetInstance()->RemoveSourceFactory( + device::GAMEPAD_SOURCE_OCULUS); } void OculusVRDeviceProvider::Initialize( - base::RepeatingCallback<void(unsigned int, + base::RepeatingCallback<void(device::mojom::XRDeviceId, mojom::VRDisplayInfoPtr, mojom::XRRuntimePtr)> add_device_callback, - base::RepeatingCallback<void(unsigned int)> remove_device_callback, + base::RepeatingCallback<void(device::mojom::XRDeviceId)> + remove_device_callback, base::OnceClosure initialization_complete) { CreateDevice(); - if (device_) - add_device_callback.Run(device_->GetId(), device_->GetVRDisplayInfo(), + if (device_) { + add_device_callback.Run(device::mojom::XRDeviceId::OCULUS_DEVICE_ID, + device_->GetVRDisplayInfo(), device_->BindXRRuntimePtr()); + + // Removed in our destructor, as VRDeviceId::OCULUS_DEVICE_ID corresponds to + // device::GAMEPAD_SOURCE_OCULUS. + GamepadDataFetcherManager::GetInstance()->AddFactory( + new IsolatedGamepadDataFetcher::Factory( + device::mojom::XRDeviceId::OCULUS_DEVICE_ID, + device_->BindGamepadFactory())); + } initialized_ = true; std::move(initialization_complete).Run(); } diff --git a/chromium/device/vr/oculus/oculus_device_provider.h b/chromium/device/vr/oculus/oculus_device_provider.h index 24791e1d617..6451c09ec71 100644 --- a/chromium/device/vr/oculus/oculus_device_provider.h +++ b/chromium/device/vr/oculus/oculus_device_provider.h @@ -12,8 +12,6 @@ #include "device/vr/vr_device_provider.h" #include "device/vr/vr_export.h" -typedef struct ovrHmdStruct* ovrSession; - namespace device { class OculusDevice; @@ -24,10 +22,11 @@ class DEVICE_VR_EXPORT OculusVRDeviceProvider : public VRDeviceProvider { ~OculusVRDeviceProvider() override; void Initialize( - base::RepeatingCallback<void(unsigned int, + base::RepeatingCallback<void(device::mojom::XRDeviceId, mojom::VRDisplayInfoPtr, mojom::XRRuntimePtr)> add_device_callback, - base::RepeatingCallback<void(unsigned int)> remove_device_callback, + base::RepeatingCallback<void(device::mojom::XRDeviceId)> + remove_device_callback, base::OnceClosure initialization_complete) override; bool Initialized() override; diff --git a/chromium/device/vr/oculus/oculus_gamepad_data_fetcher.cc b/chromium/device/vr/oculus/oculus_gamepad_data_fetcher.cc deleted file mode 100644 index aec6d3c16fe..00000000000 --- a/chromium/device/vr/oculus/oculus_gamepad_data_fetcher.cc +++ /dev/null @@ -1,253 +0,0 @@ -// 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 "device/vr/oculus/oculus_gamepad_data_fetcher.h" - -#include <memory> - -#include "base/logging.h" -#include "base/strings/utf_string_conversions.h" -#include "device/gamepad/public/cpp/gamepads.h" -#include "third_party/libovr/src/Include/OVR_CAPI.h" -#include "ui/gfx/transform.h" -#include "ui/gfx/transform_util.h" - -namespace device { - -namespace { - -float ApplyTriggerDeadzone(float value) { - // Trigger value should be between 0 and 1. We apply a deadzone for small - // values so a loose controller still reports a value of 0 when not in use. - float kTriggerDeadzone = 0.01f; - - return (value < kTriggerDeadzone) ? 0 : value; -} - -void SetGamepadButton(Gamepad* pad, - const ovrInputState& input_state, - ovrButton button_id) { - bool pressed = (input_state.Buttons & button_id) != 0; - bool touched = (input_state.Touches & button_id) != 0; - pad->buttons[pad->buttons_length].pressed = pressed; - pad->buttons[pad->buttons_length].touched = touched; - pad->buttons[pad->buttons_length].value = pressed ? 1.0f : 0.0f; - pad->buttons_length++; -} - -void SetGamepadTouchTrigger(Gamepad* pad, - const ovrInputState& input_state, - ovrTouch touch_id, - float value) { - bool touched = (input_state.Touches & touch_id) != 0; - value = ApplyTriggerDeadzone(value); - pad->buttons[pad->buttons_length].pressed = value != 0; - pad->buttons[pad->buttons_length].touched = touched; - pad->buttons[pad->buttons_length].value = value; - pad->buttons_length++; -} - -void SetGamepadTrigger(Gamepad* pad, float value) { - value = ApplyTriggerDeadzone(value); - pad->buttons[pad->buttons_length].pressed = value != 0; - pad->buttons[pad->buttons_length].touched = value != 0; - pad->buttons[pad->buttons_length].value = value; - pad->buttons_length++; -} - -void SetGamepadTouch(Gamepad* pad, - const ovrInputState& input_state, - ovrTouch touch_id) { - bool touched = (input_state.Touches & touch_id) != 0; - pad->buttons[pad->buttons_length].pressed = false; - pad->buttons[pad->buttons_length].touched = touched; - pad->buttons[pad->buttons_length].value = 0.0f; - pad->buttons_length++; -} - -void SetTouchData(PadState* state, - const ovrPoseStatef& pose, - const ovrInputState& input_state, - ovrHandType hand, - unsigned int display_id) { - if (!state) - return; - Gamepad& pad = state->data; - if (!state->is_initialized) { - state->is_initialized = true; - pad.connected = true; - pad.is_xr = true; - pad.pose.not_null = true; - pad.pose.has_orientation = true; - pad.pose.has_position = true; - pad.display_id = display_id; - switch (hand) { - case ovrHand_Left: - swprintf(pad.id, Gamepad::kIdLengthCap, L"Oculus Touch (Left)"); - pad.hand = GamepadHand::kLeft; - break; - case ovrHand_Right: - swprintf(pad.id, Gamepad::kIdLengthCap, L"Oculus Touch (Right)"); - pad.hand = GamepadHand::kRight; - break; - default: - NOTREACHED(); - return; - } - } - pad.timestamp = OculusGamepadDataFetcher::CurrentTimeInMicroseconds(); - pad.axes_length = 0; - pad.buttons_length = 0; - pad.axes[pad.axes_length++] = input_state.Thumbstick[hand].x; - pad.axes[pad.axes_length++] = -input_state.Thumbstick[hand].y; - - // This gamepad layout is the defacto standard, but can be adjusted for WebXR. - switch (hand) { - case ovrHand_Left: - SetGamepadButton(&pad, input_state, ovrButton_LThumb); - break; - case ovrHand_Right: - SetGamepadButton(&pad, input_state, ovrButton_RThumb); - break; - default: - NOTREACHED(); - break; - } - SetGamepadTouchTrigger( - &pad, input_state, - hand == ovrHand_Left ? ovrTouch_LIndexTrigger : ovrTouch_RIndexTrigger, - input_state.IndexTrigger[hand]); - SetGamepadTrigger(&pad, input_state.HandTrigger[hand]); - switch (hand) { - case ovrHand_Left: - SetGamepadButton(&pad, input_state, ovrButton_X); - SetGamepadButton(&pad, input_state, ovrButton_Y); - SetGamepadTouch(&pad, input_state, ovrTouch_LThumbRest); - break; - case ovrHand_Right: - SetGamepadButton(&pad, input_state, ovrButton_A); - SetGamepadButton(&pad, input_state, ovrButton_B); - SetGamepadTouch(&pad, input_state, ovrTouch_RThumbRest); - break; - default: - NOTREACHED(); - break; - } - - pad.pose.orientation.not_null = true; - pad.pose.orientation.x = pose.ThePose.Orientation.x; - pad.pose.orientation.y = pose.ThePose.Orientation.y; - pad.pose.orientation.z = pose.ThePose.Orientation.z; - pad.pose.orientation.w = pose.ThePose.Orientation.w; - - pad.pose.position.not_null = true; - pad.pose.position.x = pose.ThePose.Position.x; - pad.pose.position.y = pose.ThePose.Position.y; - pad.pose.position.z = pose.ThePose.Position.z; - - pad.pose.angular_velocity.not_null = true; - pad.pose.angular_velocity.x = pose.AngularVelocity.x; - pad.pose.angular_velocity.y = pose.AngularVelocity.y; - pad.pose.angular_velocity.z = pose.AngularVelocity.z; - - pad.pose.linear_velocity.not_null = true; - pad.pose.linear_velocity.x = pose.LinearVelocity.x; - pad.pose.linear_velocity.y = pose.LinearVelocity.y; - pad.pose.linear_velocity.z = pose.LinearVelocity.z; - - pad.pose.angular_acceleration.not_null = true; - pad.pose.angular_acceleration.x = pose.AngularAcceleration.x; - pad.pose.angular_acceleration.y = pose.AngularAcceleration.y; - pad.pose.angular_acceleration.z = pose.AngularAcceleration.z; - - pad.pose.linear_acceleration.not_null = true; - pad.pose.linear_acceleration.x = pose.LinearAcceleration.x; - pad.pose.linear_acceleration.y = pose.LinearAcceleration.y; - pad.pose.linear_acceleration.z = pose.LinearAcceleration.z; -} - -} // namespace - -OculusGamepadDataFetcher::Factory::Factory(unsigned int display_id, - OculusGamepadDataProvider* provider) - : display_id_(display_id), provider_(provider) { - DVLOG(1) << __FUNCTION__ << "=" << this; -} - -OculusGamepadDataFetcher::Factory::~Factory() { - DVLOG(1) << __FUNCTION__ << "=" << this; -} - -std::unique_ptr<GamepadDataFetcher> -OculusGamepadDataFetcher::Factory::CreateDataFetcher() { - return std::make_unique<OculusGamepadDataFetcher>(display_id_, provider_); -} - -GamepadSource OculusGamepadDataFetcher::Factory::source() { - return GAMEPAD_SOURCE_OCULUS; -} - -OculusGamepadDataFetcher::OculusGamepadDataFetcher( - unsigned int display_id, - OculusGamepadDataProvider* provider) - : display_id_(display_id), weak_ptr_factory_(this) { - DVLOG(1) << __FUNCTION__ << "=" << this; - - // Register for updates. - provider->RegisterDataFetcher(this); -} - -OculusGamepadDataFetcher::~OculusGamepadDataFetcher() { - DVLOG(1) << __FUNCTION__ << "=" << this; -} - -GamepadSource OculusGamepadDataFetcher::source() { - return GAMEPAD_SOURCE_OCULUS; -} - -void OculusGamepadDataFetcher::OnAddedToProvider() {} - -void OculusGamepadDataFetcher::GetGamepadData(bool devices_changed_hint) { - base::AutoLock lock(lock_); - if (data_.have_input_touch) { - SetTouchData(GetPadState(ovrControllerType_LTouch), - data_.tracking.HandPoses[ovrHand_Left], data_.input_touch, - ovrHand_Left, display_id_); - SetTouchData(GetPadState(ovrControllerType_RTouch), - data_.tracking.HandPoses[ovrHand_Right], data_.input_touch, - ovrHand_Right, display_id_); - } - - if (data_.have_input_remote) { - PadState* state = GetPadState(ovrControllerType_Remote); - if (state) { - Gamepad& pad = state->data; - if (!state->is_initialized) { - state->is_initialized = true; - swprintf(pad.id, Gamepad::kIdLengthCap, L"Oculus Remote"); - pad.connected = true; - pad.is_xr = true; - pad.display_id = display_id_; - } - pad.timestamp = CurrentTimeInMicroseconds(); - pad.axes_length = 0; - pad.buttons_length = 0; - SetGamepadButton(&pad, data_.input_remote, ovrButton_Enter); - SetGamepadButton(&pad, data_.input_remote, ovrButton_Back); - SetGamepadButton(&pad, data_.input_remote, ovrButton_Up); - SetGamepadButton(&pad, data_.input_remote, ovrButton_Down); - SetGamepadButton(&pad, data_.input_remote, ovrButton_Left); - SetGamepadButton(&pad, data_.input_remote, ovrButton_Right); - } - } -} - -void OculusGamepadDataFetcher::UpdateGamepadData(OculusInputData data) { - base::AutoLock lock(lock_); - data_ = data; -} - -void OculusGamepadDataFetcher::PauseHint(bool paused) {} - -} // namespace device diff --git a/chromium/device/vr/oculus/oculus_gamepad_data_fetcher.h b/chromium/device/vr/oculus/oculus_gamepad_data_fetcher.h deleted file mode 100644 index e3430e6163b..00000000000 --- a/chromium/device/vr/oculus/oculus_gamepad_data_fetcher.h +++ /dev/null @@ -1,72 +0,0 @@ -// 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. - -#ifndef DEVICE_VR_OCULUS_GAMEPAD_DATA_FETCHER_H_ -#define DEVICE_VR_OCULUS_GAMEPAD_DATA_FETCHER_H_ - -#include "base/synchronization/lock.h" -#include "device/gamepad/gamepad_data_fetcher.h" -#include "third_party/libovr/src/Include/OVR_CAPI.h" - -namespace device { - -class OculusGamepadDataFetcher; - -class OculusGamepadDataProvider { - public: - virtual void RegisterDataFetcher(OculusGamepadDataFetcher*) = 0; -}; - -struct OculusInputData { - ovrInputState input_touch = {}; - ovrInputState input_remote = {}; - ovrTrackingState tracking = {}; - bool have_input_touch = false; - bool have_input_remote = false; -}; - -class OculusGamepadDataFetcher : public GamepadDataFetcher { - public: - class Factory : public GamepadDataFetcherFactory { - public: - Factory(unsigned int display_id, OculusGamepadDataProvider* provider); - ~Factory() override; - std::unique_ptr<GamepadDataFetcher> CreateDataFetcher() override; - GamepadSource source() override; - - private: - unsigned int display_id_; - OculusGamepadDataProvider* provider_; - }; - - OculusGamepadDataFetcher(unsigned int display_id, - OculusGamepadDataProvider* provider); - ~OculusGamepadDataFetcher() override; - - GamepadSource source() override; - - void GetGamepadData(bool devices_changed_hint) override; - void PauseHint(bool paused) override; - void OnAddedToProvider() override; - - void UpdateGamepadData(OculusInputData data); // Called on UI thread. - - private: - unsigned int display_id_; - - // Protects access to data_, which is read/written on different threads. - base::Lock lock_; - - // have_input_* are initialized to false, so we won't try to read uninialized - // data from this if we read data_ before UpdateGamepadData is called. - OculusInputData data_; - - base::WeakPtrFactory<OculusGamepadDataFetcher> weak_ptr_factory_; - - DISALLOW_COPY_AND_ASSIGN(OculusGamepadDataFetcher); -}; - -} // namespace device - -#endif // DEVICE_VR_OCULUS_GAMEPAD_DATA_FETCHER_H_ diff --git a/chromium/device/vr/oculus/oculus_gamepad_helper.cc b/chromium/device/vr/oculus/oculus_gamepad_helper.cc new file mode 100644 index 00000000000..a4a1520ff17 --- /dev/null +++ b/chromium/device/vr/oculus/oculus_gamepad_helper.cc @@ -0,0 +1,210 @@ +// 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 "device/vr/oculus/oculus_gamepad_helper.h" + +#include <memory> + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "device/gamepad/public/cpp/gamepads.h" +#include "device/vr/vr_device.h" +#include "third_party/libovr/src/Include/OVR_CAPI.h" +#include "ui/gfx/transform.h" +#include "ui/gfx/transform_util.h" + +namespace device { + +namespace { + +enum GamepadIndex : unsigned int { + kLeftTouchController = 0x0, + kRightTouchController = 0x1, + kRemote = 0x2, +}; + +float ApplyTriggerDeadzone(float value) { + // Trigger value should be between 0 and 1. We apply a deadzone for small + // values so a loose controller still reports a value of 0 when not in use. + float kTriggerDeadzone = 0.01f; + + return (value < kTriggerDeadzone) ? 0 : value; +} + +mojom::XRGamepadButtonPtr GetGamepadButton(const ovrInputState& input_state, + ovrButton button_id) { + bool pressed = (input_state.Buttons & button_id) != 0; + bool touched = (input_state.Touches & button_id) != 0; + + auto button = mojom::XRGamepadButton::New(); + button->pressed = pressed; + button->touched = touched; + button->value = pressed ? 1.0f : 0.0f; + + return button; +} + +mojom::XRGamepadButtonPtr GetGamepadTouchTrigger( + const ovrInputState& input_state, + ovrTouch touch_id, + float value) { + bool touched = (input_state.Touches & touch_id) != 0; + value = ApplyTriggerDeadzone(value); + + auto button = mojom::XRGamepadButton::New(); + button->pressed = value != 0; + button->touched = touched; + button->value = value; + + return button; +} + +mojom::XRGamepadButtonPtr GetGamepadTrigger(float value) { + value = ApplyTriggerDeadzone(value); + auto button = mojom::XRGamepadButton::New(); + button->pressed = value != 0; + button->touched = value != 0; + button->value = value; + + return button; +} + +mojom::XRGamepadButtonPtr GetGamepadTouch(const ovrInputState& input_state, + ovrTouch touch_id) { + bool touched = (input_state.Touches & touch_id) != 0; + + auto button = mojom::XRGamepadButton::New(); + button->pressed = false; + button->touched = touched; + button->value = 0.0f; + + return button; +} + +void AddTouchData(mojom::XRGamepadDataPtr& data, + const ovrTrackingState& tracking, + const ovrInputState& input_state, + ovrHandType hand) { + auto gamepad = mojom::XRGamepad::New(); + // This gamepad layout is the defacto standard, but can be adjusted for WebXR. + switch (hand) { + case ovrHand_Left: + gamepad->hand = device::mojom::XRHandedness::LEFT; + gamepad->controller_id = kLeftTouchController; + gamepad->buttons.push_back( + GetGamepadButton(input_state, ovrButton_LThumb)); + break; + case ovrHand_Right: + gamepad->hand = device::mojom::XRHandedness::RIGHT; + gamepad->controller_id = kRightTouchController; + gamepad->buttons.push_back( + GetGamepadButton(input_state, ovrButton_RThumb)); + break; + default: + NOTREACHED(); + return; + } + + gamepad->axes.push_back(input_state.Thumbstick[hand].x); + gamepad->axes.push_back(-input_state.Thumbstick[hand].y); + + gamepad->buttons.push_back(GetGamepadTouchTrigger( + input_state, + hand == ovrHand_Left ? ovrTouch_LIndexTrigger : ovrTouch_RIndexTrigger, + input_state.IndexTrigger[hand])); + gamepad->buttons.push_back(GetGamepadTrigger(input_state.HandTrigger[hand])); + + switch (hand) { + case ovrHand_Left: + gamepad->buttons.push_back(GetGamepadButton(input_state, ovrButton_X)); + gamepad->buttons.push_back(GetGamepadButton(input_state, ovrButton_Y)); + gamepad->buttons.push_back( + GetGamepadTouch(input_state, ovrTouch_LThumbRest)); + break; + case ovrHand_Right: + gamepad->buttons.push_back(GetGamepadButton(input_state, ovrButton_A)); + gamepad->buttons.push_back(GetGamepadButton(input_state, ovrButton_B)); + gamepad->buttons.push_back( + GetGamepadTouch(input_state, ovrTouch_RThumbRest)); + break; + default: + NOTREACHED(); + break; + } + + auto dst_pose = mojom::VRPose::New(); + const ovrPoseStatef& src_pose = tracking.HandPoses[hand]; + + dst_pose->orientation = std::vector<float>( + {src_pose.ThePose.Orientation.x, src_pose.ThePose.Orientation.y, + src_pose.ThePose.Orientation.z, src_pose.ThePose.Orientation.w}); + + dst_pose->position = std::vector<float>({src_pose.ThePose.Position.x, + src_pose.ThePose.Position.y, + src_pose.ThePose.Position.z}); + + dst_pose->angularVelocity = std::vector<float>({src_pose.AngularVelocity.x, + src_pose.AngularVelocity.y, + src_pose.AngularVelocity.z}); + + dst_pose->linearVelocity = + std::vector<float>({src_pose.LinearVelocity.x, src_pose.LinearVelocity.y, + src_pose.LinearVelocity.z}); + + dst_pose->angularAcceleration = std::vector<float>( + {src_pose.AngularAcceleration.x, src_pose.AngularAcceleration.y, + src_pose.AngularAcceleration.z}); + + dst_pose->linearAcceleration = std::vector<float>( + {src_pose.LinearAcceleration.x, src_pose.LinearAcceleration.y, + src_pose.LinearAcceleration.z}); + + gamepad->pose = std::move(dst_pose); + gamepad->can_provide_position = true; + gamepad->can_provide_orientation = true; + data->gamepads.push_back(std::move(gamepad)); +} + +void AddRemoteData(mojom::XRGamepadDataPtr& data, + const ovrInputState& input_remote) { + auto remote = mojom::XRGamepad::New(); + remote->buttons.push_back(GetGamepadButton(input_remote, ovrButton_Enter)); + remote->buttons.push_back(GetGamepadButton(input_remote, ovrButton_Back)); + remote->buttons.push_back(GetGamepadButton(input_remote, ovrButton_Up)); + remote->buttons.push_back(GetGamepadButton(input_remote, ovrButton_Down)); + remote->buttons.push_back(GetGamepadButton(input_remote, ovrButton_Left)); + remote->buttons.push_back(GetGamepadButton(input_remote, ovrButton_Right)); + remote->controller_id = kRemote; + remote->hand = device::mojom::XRHandedness::NONE; + data->gamepads.push_back(std::move(remote)); +} + +} // namespace + +mojom::XRGamepadDataPtr OculusGamepadHelper::GetGamepadData( + ovrSession session) { + ovrInputState input_touch; + bool have_touch = OVR_SUCCESS( + ovr_GetInputState(session, ovrControllerType_Touch, &input_touch)); + + ovrInputState input_remote; + bool have_remote = OVR_SUCCESS( + ovr_GetInputState(session, ovrControllerType_Remote, &input_remote)); + + ovrTrackingState tracking = ovr_GetTrackingState(session, 0, false); + + mojom::XRGamepadDataPtr data = mojom::XRGamepadData::New(); + + if (have_touch) { + AddTouchData(data, tracking, input_touch, ovrHand_Left); + AddTouchData(data, tracking, input_touch, ovrHand_Right); + } + + if (have_remote) + AddRemoteData(data, input_remote); + + return data; +} + +} // namespace device diff --git a/chromium/device/vr/oculus/oculus_gamepad_helper.h b/chromium/device/vr/oculus/oculus_gamepad_helper.h new file mode 100644 index 00000000000..0e4f690072c --- /dev/null +++ b/chromium/device/vr/oculus/oculus_gamepad_helper.h @@ -0,0 +1,20 @@ +// 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. + +#ifndef DEVICE_VR_OCULUS_OCULUS_GAMEPAD_HELPER_H_ +#define DEVICE_VR_OCULUS_OCULUS_GAMEPAD_HELPER_H_ + +#include "device/vr/public/mojom/isolated_xr_service.mojom.h" +#include "third_party/libovr/src/Include/OVR_CAPI.h" + +namespace device { + +class OculusGamepadHelper { + public: + static mojom::XRGamepadDataPtr GetGamepadData(ovrSession session); +}; + +} // namespace device + +#endif // DEVICE_VR_OCULUS_OCULUS_GAMEPAD_HELPER_H_ diff --git a/chromium/device/vr/oculus/oculus_render_loop.cc b/chromium/device/vr/oculus/oculus_render_loop.cc index 80a37b6ec6e..d4348ae246c 100644 --- a/chromium/device/vr/oculus/oculus_render_loop.cc +++ b/chromium/device/vr/oculus/oculus_render_loop.cc @@ -4,6 +4,7 @@ #include "device/vr/oculus/oculus_render_loop.h" +#include "device/vr/oculus/oculus_gamepad_helper.h" #include "device/vr/oculus/oculus_type_converters.h" #include "third_party/libovr/src/Include/Extras/OVR_Math.h" #include "third_party/libovr/src/Include/OVR_CAPI.h" @@ -42,15 +43,13 @@ gfx::Transform PoseToTransform(const ovrPosef& pose) { } // namespace OculusRenderLoop::OculusRenderLoop( - base::RepeatingCallback<void()> on_presentation_ended, - base::RepeatingCallback< - void(ovrInputState, ovrInputState, ovrTrackingState, bool, bool)> - on_controller_updated) + base::RepeatingCallback<void()> on_presentation_ended) : base::Thread("OculusRenderLoop"), main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()), - binding_(this), + presentation_binding_(this), + frame_data_binding_(this), on_presentation_ended_(on_presentation_ended), - on_controller_updated_(on_controller_updated), + gamepad_provider_(this), weak_ptr_factory_(this) { DCHECK(main_thread_task_runner_); } @@ -69,7 +68,9 @@ void OculusRenderLoop::ClearPendingFrame() { void OculusRenderLoop::CleanUp() { submit_client_ = nullptr; StopOvrSession(); - binding_.Close(); + presentation_binding_.Close(); + frame_data_binding_.Close(); + gamepad_provider_.Close(); } void OculusRenderLoop::SubmitFrameMissing(int16_t frame_index, @@ -228,9 +229,8 @@ void OculusRenderLoop::UpdateLayerBounds(int16_t frame_id, DestroyOvrSwapChain(); }; -void OculusRenderLoop::RequestSession( - mojom::XRDeviceRuntimeSessionOptionsPtr options, - RequestSessionCallback callback) { +void OculusRenderLoop::RequestSession(mojom::XRRuntimeSessionOptionsPtr options, + RequestSessionCallback callback) { DCHECK(options->immersive); StartOvrSession(); @@ -241,28 +241,36 @@ void OculusRenderLoop::RequestSession( #endif ) { main_thread_task_runner_->PostTask( - FROM_HERE, - base::BindOnce(std::move(callback), false, nullptr, nullptr, nullptr)); + FROM_HERE, base::BindOnce(std::move(callback), false, nullptr)); return; } - binding_.Close(); - device::mojom::VRPresentationProviderPtr provider; - binding_.Bind(mojo::MakeRequest(&provider)); + presentation_binding_.Close(); + frame_data_binding_.Close(); + device::mojom::XRPresentationProviderPtr presentation_provider; + device::mojom::XRFrameDataProviderPtr frame_data_provider; + presentation_binding_.Bind(mojo::MakeRequest(&presentation_provider)); + frame_data_binding_.Bind(mojo::MakeRequest(&frame_data_provider)); - device::mojom::VRDisplayFrameTransportOptionsPtr transport_options = - device::mojom::VRDisplayFrameTransportOptions::New(); + device::mojom::XRPresentationTransportOptionsPtr transport_options = + device::mojom::XRPresentationTransportOptions::New(); transport_options->transport_method = - device::mojom::VRDisplayFrameTransportMethod::SUBMIT_AS_TEXTURE_HANDLE; + device::mojom::XRPresentationTransportMethod::SUBMIT_AS_TEXTURE_HANDLE; // Only set boolean options that we need. Default is false, and we should be // able to safely ignore ones that our implementation doesn't care about. transport_options->wait_for_transfer_notification = true; + auto submit_frame_sink = device::mojom::XRPresentationConnection::New(); + submit_frame_sink->provider = presentation_provider.PassInterface(); + submit_frame_sink->client_request = mojo::MakeRequest(&submit_client_); + submit_frame_sink->transport_options = std::move(transport_options); + + auto session = device::mojom::XRSession::New(); + session->data_provider = frame_data_provider.PassInterface(); + session->submit_frame_sink = std::move(submit_frame_sink); + main_thread_task_runner_->PostTask( - FROM_HERE, - base::BindOnce(std::move(callback), true, - mojo::MakeRequest(&submit_client_), - provider.PassInterface(), std::move(transport_options))); + FROM_HERE, base::BindOnce(std::move(callback), true, std::move(session))); is_presenting_ = true; } @@ -296,7 +304,8 @@ void OculusRenderLoop::StopOvrSession() { void OculusRenderLoop::ExitPresent() { is_presenting_ = false; - binding_.Close(); + presentation_binding_.Close(); + frame_data_binding_.Close(); submit_client_ = nullptr; ClearPendingFrame(); @@ -312,7 +321,7 @@ base::WeakPtr<OculusRenderLoop> OculusRenderLoop::GetWeakPtr() { } void OculusRenderLoop::GetFrameData( - mojom::VRPresentationProvider::GetFrameDataCallback callback) { + mojom::XRFrameDataProvider::GetFrameDataCallback callback) { DCHECK(is_presenting_); if (has_outstanding_frame_) { @@ -352,6 +361,14 @@ void OculusRenderLoop::GetFrameData( std::move(callback).Run(std::move(frame_data)); } +void OculusRenderLoop::RequestGamepadProvider( + mojom::IsolatedXRGamepadProviderRequest request) { + gamepad_provider_.Close(); + // We just close the binding, so the other side won't expect callbacks. + gamepad_callback_.Reset(); + gamepad_provider_.Bind(std::move(request)); +} + std::vector<mojom::XRInputSourceStatePtr> OculusRenderLoop::GetInputState( const ovrTrackingState& tracking_state) { std::vector<mojom::XRInputSourceStatePtr> input_states; @@ -405,28 +422,18 @@ std::vector<mojom::XRInputSourceStatePtr> OculusRenderLoop::GetInputState( } void OculusRenderLoop::UpdateControllerState() { - if (!session_) { - ovrInputState input = {}; - ovrTrackingState tracking = {}; - main_thread_task_runner_->PostTask( - FROM_HERE, base::BindOnce(on_controller_updated_, input, input, - tracking, false, false)); + if (!gamepad_callback_) { + // Nobody is listening to updates, so bail early. + return; } - ovrInputState input_touch; - bool have_touch = OVR_SUCCESS( - ovr_GetInputState(session_, ovrControllerType_Touch, &input_touch)); - - ovrInputState input_remote; - bool have_remote = OVR_SUCCESS( - ovr_GetInputState(session_, ovrControllerType_Remote, &input_remote)); - - ovrTrackingState tracking = ovr_GetTrackingState(session_, 0, false); + if (!session_) { + std::move(gamepad_callback_).Run(nullptr); + return; + } - main_thread_task_runner_->PostTask( - FROM_HERE, - base::BindOnce(on_controller_updated_, input_touch, input_remote, - tracking, have_touch, have_remote)); + std::move(gamepad_callback_) + .Run(OculusGamepadHelper::GetGamepadData(session_)); } device::mojom::XRInputSourceStatePtr OculusRenderLoop::GetTouchData( @@ -487,4 +494,22 @@ device::mojom::XRInputSourceStatePtr OculusRenderLoop::GetTouchData( return state; } +void OculusRenderLoop::RequestUpdate( + mojom::IsolatedXRGamepadProvider::RequestUpdateCallback callback) { + DCHECK(!gamepad_callback_); + if (gamepad_callback_) { + std::move(gamepad_callback_).Run(nullptr); + } + + // If we aren't presenting, reply now saying that we have no controllers. + if (!is_presenting_) { + std::move(callback).Run(nullptr); + return; + } + + // Otherwise, save the callback to resolve next time we update (typically on + // vsync). + gamepad_callback_ = std::move(callback); +} + } // namespace device diff --git a/chromium/device/vr/oculus/oculus_render_loop.h b/chromium/device/vr/oculus/oculus_render_loop.h index c8186b71aea..18ac6946d94 100644 --- a/chromium/device/vr/oculus/oculus_render_loop.h +++ b/chromium/device/vr/oculus/oculus_render_loop.h @@ -23,27 +23,27 @@ namespace device { const int kMaxOculusRenderLoopInputId = (ovrControllerType_Remote + 1); -class OculusRenderLoop : public base::Thread, mojom::VRPresentationProvider { +class OculusRenderLoop : public base::Thread, + mojom::XRPresentationProvider, + mojom::XRFrameDataProvider, + mojom::IsolatedXRGamepadProvider { public: using RequestSessionCallback = - base::OnceCallback<void(bool result, - mojom::VRSubmitFrameClientRequest, - mojom::VRPresentationProviderPtrInfo, - mojom::VRDisplayFrameTransportOptionsPtr)>; - - OculusRenderLoop( - base::RepeatingCallback<void()> on_presentation_ended, - base::RepeatingCallback< - void(ovrInputState, ovrInputState, ovrTrackingState, bool, bool)> - on_controller_updated); + base::OnceCallback<void(bool result, mojom::XRSessionPtr)>; + + OculusRenderLoop(base::RepeatingCallback<void()> on_presentation_ended); ~OculusRenderLoop() override; - void RequestSession(mojom::XRDeviceRuntimeSessionOptionsPtr options, + void RequestSession(mojom::XRRuntimeSessionOptionsPtr options, RequestSessionCallback callback); void ExitPresent(); base::WeakPtr<OculusRenderLoop> GetWeakPtr(); - // VRPresentationProvider overrides: + // IsolatedXRGamepadProvider + void RequestUpdate(mojom::IsolatedXRGamepadProvider::RequestUpdateCallback + callback) override; + + // XRPresentationProvider overrides: void SubmitFrameMissing(int16_t frame_index, const gpu::SyncToken&) override; void SubmitFrame(int16_t frame_index, const gpu::MailboxHolder& mailbox, @@ -58,7 +58,11 @@ class OculusRenderLoop : public base::Thread, mojom::VRPresentationProvider { const gfx::RectF& right_bounds, const gfx::Size& source_size) override; void GetFrameData( - mojom::VRPresentationProvider::GetFrameDataCallback callback) override; + mojom::XRFrameDataProvider::GetFrameDataCallback callback) override; + + // Bind a gamepad provider on the render loop thread, so we can provide + // updates with the latest poses used for rendering. + void RequestGamepadProvider(mojom::IsolatedXRGamepadProviderRequest request); private: // base::Thread overrides: @@ -91,6 +95,8 @@ class OculusRenderLoop : public base::Thread, mojom::VRPresentationProvider { base::OnceCallback<void()> delayed_get_frame_data_callback_; bool has_outstanding_frame_ = false; + mojom::IsolatedXRGamepadProvider::RequestUpdateCallback gamepad_callback_; + long long ovr_frame_index_ = 0; int16_t next_frame_id_ = 0; bool is_presenting_ = false; @@ -98,18 +104,18 @@ class OculusRenderLoop : public base::Thread, mojom::VRPresentationProvider { gfx::RectF right_bounds_; gfx::Size source_size_; scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_; - mojom::VRSubmitFrameClientPtr submit_client_; + mojom::XRPresentationClientPtr submit_client_; ovrSession session_ = nullptr; ovrGraphicsLuid luid_ = {}; ovrPosef last_render_pose_; ovrTextureSwapChain texture_swap_chain_ = 0; double sensor_time_; - mojo::Binding<mojom::VRPresentationProvider> binding_; + mojo::Binding<mojom::XRPresentationProvider> presentation_binding_; + mojo::Binding<mojom::XRFrameDataProvider> frame_data_binding_; bool primary_input_pressed[kMaxOculusRenderLoopInputId]; base::RepeatingCallback<void()> on_presentation_ended_; - base::RepeatingCallback< - void(ovrInputState, ovrInputState, ovrTrackingState, bool, bool)> - on_controller_updated_; + + mojo::Binding<mojom::IsolatedXRGamepadProvider> gamepad_provider_; base::WeakPtrFactory<OculusRenderLoop> weak_ptr_factory_; diff --git a/chromium/device/vr/openvr/openvr_device.cc b/chromium/device/vr/openvr/openvr_device.cc index cbab3b27b52..8334f97e40a 100644 --- a/chromium/device/vr/openvr/openvr_device.cc +++ b/chromium/device/vr/openvr/openvr_device.cc @@ -10,7 +10,6 @@ #include "base/memory/ptr_util.h" #include "base/numerics/math_constants.h" #include "build/build_config.h" -#include "device/vr/openvr/openvr_gamepad_data_fetcher.h" #include "device/vr/openvr/openvr_render_loop.h" #include "device/vr/openvr/openvr_type_converters.h" #include "third_party/openvr/src/headers/openvr.h" @@ -63,9 +62,9 @@ std::vector<float> HmdMatrix34ToWebVRTransformMatrix( } mojom::VRDisplayInfoPtr CreateVRDisplayInfo(vr::IVRSystem* vr_system, - unsigned int id) { + device::mojom::XRDeviceId id) { mojom::VRDisplayInfoPtr display_info = mojom::VRDisplayInfo::New(); - display_info->index = id; + display_info->id = id; display_info->displayName = GetOpenVRString(vr_system, vr::Prop_ManufacturerName_String) + " " + GetOpenVRString(vr_system, vr::Prop_ModelNumber_String); @@ -129,9 +128,10 @@ mojom::VRDisplayInfoPtr CreateVRDisplayInfo(vr::IVRSystem* vr_system, } // namespace OpenVRDevice::OpenVRDevice() - : VRDeviceBase(VRDeviceId::OPENVR_DEVICE_ID), + : VRDeviceBase(device::mojom::XRDeviceId::OPENVR_DEVICE_ID), main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()), exclusive_controller_binding_(this), + gamepad_provider_factory_binding_(this), weak_ptr_factory_(this) { // Initialize OpenVR. openvr_ = std::make_unique<OpenVRWrapper>(false /* presenting */); @@ -142,20 +142,15 @@ OpenVRDevice::OpenVRDevice() SetVRDisplayInfo(CreateVRDisplayInfo(openvr_->GetSystem(), GetId())); - render_loop_ = std::make_unique<OpenVRRenderLoop>(base::BindRepeating( - &OpenVRDevice::OnGamepadUpdated, weak_ptr_factory_.GetWeakPtr())); + render_loop_ = std::make_unique<OpenVRRenderLoop>(); OnPollingEvents(); } -void OpenVRDevice::OnGamepadUpdated(OpenVRGamepadState state) { - if (gamepad_data_fetcher_) { - gamepad_data_fetcher_->UpdateGamepadData(state); - } -} - -void OpenVRDevice::RegisterDataFetcher(OpenVRGamepadDataFetcher* fetcher) { - gamepad_data_fetcher_ = fetcher; +mojom::IsolatedXRGamepadProviderFactoryPtr OpenVRDevice::BindGamepadFactory() { + mojom::IsolatedXRGamepadProviderFactoryPtr ret; + gamepad_provider_factory_binding_.Bind(mojo::MakeRequest(&ret)); + return ret; } OpenVRDevice::~OpenVRDevice() { @@ -171,14 +166,27 @@ void OpenVRDevice::Shutdown() { } void OpenVRDevice::RequestSession( - mojom::XRDeviceRuntimeSessionOptionsPtr options, + mojom::XRRuntimeSessionOptionsPtr options, mojom::XRRuntime::RequestSessionCallback callback) { - if (!render_loop_->IsRunning()) - render_loop_->Start(); + if (!options->immersive) { + ReturnNonImmersiveSession(std::move(callback)); + return; + } if (!render_loop_->IsRunning()) { - std::move(callback).Run(nullptr, nullptr); - return; + render_loop_->Start(); + + if (!render_loop_->IsRunning()) { + std::move(callback).Run(nullptr, nullptr); + return; + } + + if (provider_request_) { + render_loop_->task_runner()->PostTask( + FROM_HERE, base::BindOnce(&OpenVRRenderLoop::RequestGamepadProvider, + render_loop_->GetWeakPtr(), + std::move(provider_request_))); + } } // We are done using OpenVR until the presentation session ends. @@ -211,9 +219,7 @@ void OpenVRDevice::OnPresentationEnded() { void OpenVRDevice::OnRequestSessionResult( mojom::XRRuntime::RequestSessionCallback callback, bool result, - mojom::VRSubmitFrameClientRequest request, - mojom::VRPresentationProviderPtrInfo provider_info, - mojom::VRDisplayFrameTransportOptionsPtr transport_options) { + mojom::XRSessionPtr session) { if (!result) { OnPresentationEnded(); std::move(callback).Run(nullptr, nullptr); @@ -222,11 +228,6 @@ void OpenVRDevice::OnRequestSessionResult( OnStartPresenting(); - auto connection = mojom::XRPresentationConnection::New(); - connection->client_request = std::move(request); - connection->provider = std::move(provider_info); - connection->transport_options = std::move(transport_options); - mojom::XRSessionControllerPtr session_controller; exclusive_controller_binding_.Bind(mojo::MakeRequest(&session_controller)); @@ -236,7 +237,21 @@ void OpenVRDevice::OnRequestSessionResult( base::BindOnce(&OpenVRDevice::OnPresentingControllerMojoConnectionError, base::Unretained(this))); - std::move(callback).Run(std::move(connection), std::move(session_controller)); + session->display_info = display_info_.Clone(); + + std::move(callback).Run(std::move(session), std::move(session_controller)); +} + +void OpenVRDevice::GetIsolatedXRGamepadProvider( + mojom::IsolatedXRGamepadProviderRequest provider_request) { + if (render_loop_->IsRunning()) { + render_loop_->task_runner()->PostTask( + FROM_HERE, base::BindOnce(&OpenVRRenderLoop::RequestGamepadProvider, + render_loop_->GetWeakPtr(), + std::move(provider_request))); + } else { + provider_request_ = std::move(provider_request); + } } // XRSessionController @@ -249,13 +264,16 @@ void OpenVRDevice::OnPresentingControllerMojoConnectionError() { render_loop_->task_runner()->PostTask( FROM_HERE, base::Bind(&OpenVRRenderLoop::ExitPresent, render_loop_->GetWeakPtr())); - render_loop_->Stop(); + // Don't stop the render loop here. We need to keep the gamepad provider alive + // so that we don't lose a pending mojo gamepad_callback_. + // TODO(https://crbug.com/875187): Alternatively, we could recreate the + // provider on the next session, or look into why the callback gets lost. OnExitPresent(); exclusive_controller_binding_.Close(); } void OpenVRDevice::OnMagicWindowFrameDataRequest( - mojom::VRPresentationProvider::GetFrameDataCallback callback) { + mojom::XRFrameDataProvider::GetFrameDataCallback callback) { if (!openvr_) { std::move(callback).Run(nullptr); return; diff --git a/chromium/device/vr/openvr/openvr_device.h b/chromium/device/vr/openvr/openvr_device.h index c5af92f2451..6a873889e44 100644 --- a/chromium/device/vr/openvr/openvr_device.h +++ b/chromium/device/vr/openvr/openvr_device.h @@ -10,7 +10,6 @@ #include "base/macros.h" #include "base/single_thread_task_runner.h" #include "device/vr/openvr/openvr_api_wrapper.h" -#include "device/vr/openvr/openvr_gamepad_data_fetcher.h" #include "device/vr/public/mojom/vr_service.mojom.h" #include "device/vr/vr_device_base.h" #include "mojo/public/cpp/bindings/binding.h" @@ -18,11 +17,11 @@ namespace device { class OpenVRRenderLoop; -struct OpenVRGamepadState; -class OpenVRDevice : public VRDeviceBase, - public mojom::XRSessionController, - public OpenVRGamepadDataProvider { +class DEVICE_VR_EXPORT OpenVRDevice + : public VRDeviceBase, + public mojom::XRSessionController, + public mojom::IsolatedXRGamepadProviderFactory { public: OpenVRDevice(); ~OpenVRDevice() override; @@ -31,41 +30,43 @@ class OpenVRDevice : public VRDeviceBase, // VRDeviceBase void RequestSession( - mojom::XRDeviceRuntimeSessionOptionsPtr options, + mojom::XRRuntimeSessionOptionsPtr options, mojom::XRRuntime::RequestSessionCallback callback) override; void OnPollingEvents(); - void OnRequestSessionResult( - mojom::XRRuntime::RequestSessionCallback callback, - bool result, - mojom::VRSubmitFrameClientRequest request, - mojom::VRPresentationProviderPtrInfo provider_info, - mojom::VRDisplayFrameTransportOptionsPtr transport_options); + void OnRequestSessionResult(mojom::XRRuntime::RequestSessionCallback callback, + bool result, + mojom::XRSessionPtr session); bool IsInitialized() { return !!openvr_; } + mojom::IsolatedXRGamepadProviderFactoryPtr BindGamepadFactory(); + private: // VRDeviceBase void OnMagicWindowFrameDataRequest( - mojom::VRMagicWindowProvider::GetFrameDataCallback callback) override; + mojom::XRFrameDataProvider::GetFrameDataCallback callback) override; // XRSessionController void SetFrameDataRestricted(bool restricted) override; + // mojom::IsolatedXRGamepadProviderFactory + void GetIsolatedXRGamepadProvider( + mojom::IsolatedXRGamepadProviderRequest provider_request) override; + void OnPresentingControllerMojoConnectionError(); void OnPresentationEnded(); - void RegisterDataFetcher(OpenVRGamepadDataFetcher*) override; - void OnGamepadUpdated(OpenVRGamepadState state); - std::unique_ptr<OpenVRRenderLoop> render_loop_; - mojom::VRDisplayInfoPtr display_info_; std::unique_ptr<OpenVRWrapper> openvr_; scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_; mojo::Binding<mojom::XRSessionController> exclusive_controller_binding_; - OpenVRGamepadDataFetcher* gamepad_data_fetcher_ = nullptr; + + mojo::Binding<mojom::IsolatedXRGamepadProviderFactory> + gamepad_provider_factory_binding_; + mojom::IsolatedXRGamepadProviderRequest provider_request_; base::WeakPtrFactory<OpenVRDevice> weak_ptr_factory_; diff --git a/chromium/device/vr/openvr/openvr_device_provider.cc b/chromium/device/vr/openvr/openvr_device_provider.cc index 2e1e55a7e73..377eca334ba 100644 --- a/chromium/device/vr/openvr/openvr_device_provider.cc +++ b/chromium/device/vr/openvr/openvr_device_provider.cc @@ -6,9 +6,9 @@ #include "base/metrics/histogram_macros.h" #include "device/gamepad/gamepad_data_fetcher_manager.h" +#include "device/vr/isolated_gamepad_data_fetcher.h" #include "device/vr/openvr/openvr_api_wrapper.h" #include "device/vr/openvr/openvr_device.h" -#include "device/vr/openvr/openvr_gamepad_data_fetcher.h" #include "device/vr/openvr/test/test_hook.h" #include "third_party/openvr/src/headers/openvr.h" @@ -37,15 +37,17 @@ OpenVRDeviceProvider::~OpenVRDeviceProvider() { } void OpenVRDeviceProvider::Initialize( - base::RepeatingCallback<void(unsigned int, + base::RepeatingCallback<void(device::mojom::XRDeviceId, mojom::VRDisplayInfoPtr, mojom::XRRuntimePtr)> add_device_callback, - base::RepeatingCallback<void(unsigned int)> remove_device_callback, + base::RepeatingCallback<void(device::mojom::XRDeviceId)> + remove_device_callback, base::OnceClosure initialization_complete) { CreateDevice(); - if (device_) + if (device_) { add_device_callback.Run(device_->GetId(), device_->GetVRDisplayInfo(), device_->BindXRRuntimePtr()); + } initialized_ = true; std::move(initialization_complete).Run(); } @@ -61,7 +63,9 @@ void OpenVRDeviceProvider::CreateDevice() { device_ = std::make_unique<OpenVRDevice>(); if (device_->IsInitialized()) { GamepadDataFetcherManager::GetInstance()->AddFactory( - new OpenVRGamepadDataFetcher::Factory(device_->GetId(), device_.get())); + new IsolatedGamepadDataFetcher::Factory( + device::mojom::XRDeviceId::OPENVR_DEVICE_ID, + device_->BindGamepadFactory())); } else { device_ = nullptr; } diff --git a/chromium/device/vr/openvr/openvr_device_provider.h b/chromium/device/vr/openvr/openvr_device_provider.h index 7dc8229ddc4..ed9f42855a1 100644 --- a/chromium/device/vr/openvr/openvr_device_provider.h +++ b/chromium/device/vr/openvr/openvr_device_provider.h @@ -23,10 +23,11 @@ class DEVICE_VR_EXPORT OpenVRDeviceProvider : public VRDeviceProvider { ~OpenVRDeviceProvider() override; void Initialize( - base::RepeatingCallback<void(unsigned int, + base::RepeatingCallback<void(device::mojom::XRDeviceId, mojom::VRDisplayInfoPtr, mojom::XRRuntimePtr)> add_device_callback, - base::RepeatingCallback<void(unsigned int)> remove_device_callback, + base::RepeatingCallback<void(device::mojom::XRDeviceId)> + remove_device_callback, base::OnceClosure initialization_complete) override; bool Initialized() override; diff --git a/chromium/device/vr/openvr/openvr_gamepad_data_fetcher.cc b/chromium/device/vr/openvr/openvr_gamepad_data_fetcher.cc deleted file mode 100644 index 0f301411ee0..00000000000 --- a/chromium/device/vr/openvr/openvr_gamepad_data_fetcher.cc +++ /dev/null @@ -1,218 +0,0 @@ -// 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 "device/vr/openvr/openvr_gamepad_data_fetcher.h" - -#include <memory> - -#include "base/logging.h" -#include "base/strings/utf_string_conversions.h" -#include "device/gamepad/public/cpp/gamepads.h" -#include "third_party/openvr/src/headers/openvr.h" -#include "ui/gfx/transform.h" -#include "ui/gfx/transform_util.h" - -namespace device { - -namespace { - -void SetGamepadButton(Gamepad* pad, - const vr::VRControllerState_t& controller_state, - uint64_t supported_buttons, - vr::EVRButtonId button_id) { - uint64_t button_mask = vr::ButtonMaskFromId(button_id); - if ((supported_buttons & button_mask) != 0) { - bool button_pressed = (controller_state.ulButtonPressed & button_mask) != 0; - bool button_touched = (controller_state.ulButtonTouched & button_mask) != 0; - pad->buttons[pad->buttons_length].touched = button_touched; - pad->buttons[pad->buttons_length].pressed = button_pressed; - pad->buttons[pad->buttons_length].value = button_pressed ? 1.0 : 0.0; - pad->buttons_length++; - } -} - -} // namespace - -OpenVRGamepadDataFetcher::Factory::Factory(unsigned int display_id, - OpenVRGamepadDataProvider* provider) - : display_id_(display_id), provider_(provider) { - DVLOG(1) << __FUNCTION__ << "=" << this; -} - -OpenVRGamepadDataFetcher::Factory::~Factory() { - DVLOG(1) << __FUNCTION__ << "=" << this; -} - -std::unique_ptr<GamepadDataFetcher> -OpenVRGamepadDataFetcher::Factory::CreateDataFetcher() { - return std::make_unique<OpenVRGamepadDataFetcher>(display_id_, provider_); -} - -GamepadSource OpenVRGamepadDataFetcher::Factory::source() { - return GAMEPAD_SOURCE_OPENVR; -} - -OpenVRGamepadDataFetcher::OpenVRGamepadDataFetcher( - unsigned int display_id, - OpenVRGamepadDataProvider* provider) - : display_id_(display_id) { - DVLOG(1) << __FUNCTION__ << "=" << this; - - // Register for updates. - provider->RegisterDataFetcher(this); -} - -OpenVRGamepadDataFetcher::~OpenVRGamepadDataFetcher() { - DVLOG(1) << __FUNCTION__ << "=" << this; -} - -GamepadSource OpenVRGamepadDataFetcher::source() { - return GAMEPAD_SOURCE_OPENVR; -} - -void OpenVRGamepadDataFetcher::OnAddedToProvider() {} - -void OpenVRGamepadDataFetcher::GetGamepadData(bool devices_changed_hint) { - base::AutoLock lock(lock_); - - vr::TrackedDevicePose_t* tracked_devices_poses = data_.tracked_devices_poses; - for (uint32_t i = 0; i < vr::k_unMaxTrackedDeviceCount; ++i) { - if (data_.device_class[i] != vr::TrackedDeviceClass_Controller) - continue; - - PadState* state = GetPadState(i); - if (!state) - continue; - - Gamepad& pad = state->data; - - vr::VRControllerState_t& controller_state = data_.controller_state[i]; - if (data_.have_controller_state[i]) { - pad.timestamp = CurrentTimeInMicroseconds(); - pad.connected = true; - pad.is_xr = true; - - pad.pose.not_null = true; - - pad.pose.has_orientation = true; - pad.pose.has_position = true; - - // The defacto standard says we set id to "OpenVR Gamepad". WebXR input - // will provide a better solution. - swprintf(pad.id, Gamepad::kIdLengthCap, L"OpenVR Gamepad"); - swprintf(pad.mapping, Gamepad::kMappingLengthCap, L""); - - pad.display_id = display_id_; - - vr::ETrackedControllerRole hand = data_.hand[i]; - - switch (hand) { - case vr::TrackedControllerRole_Invalid: - pad.hand = GamepadHand::kNone; - break; - case vr::TrackedControllerRole_LeftHand: - pad.hand = GamepadHand::kLeft; - break; - case vr::TrackedControllerRole_RightHand: - pad.hand = GamepadHand::kRight; - break; - } - - uint64_t supported_buttons = data_.supported_buttons[i]; - - pad.buttons_length = 0; - pad.axes_length = 0; - - for (unsigned int j = 0; j < vr::k_unControllerStateAxisCount; ++j) { - int32_t axis_type = data_.axis_type[i][j]; - switch (axis_type) { - case vr::k_eControllerAxis_Joystick: - case vr::k_eControllerAxis_TrackPad: - pad.axes[pad.axes_length++] = controller_state.rAxis[j].x; - pad.axes[pad.axes_length++] = controller_state.rAxis[j].y; - - SetGamepadButton( - &pad, controller_state, supported_buttons, - static_cast<vr::EVRButtonId>(vr::k_EButton_Axis0 + j)); - - break; - case vr::k_eControllerAxis_Trigger: - pad.buttons[pad.buttons_length].value = controller_state.rAxis[j].x; - - uint64_t button_mask = vr::ButtonMaskFromId( - static_cast<vr::EVRButtonId>(vr::k_EButton_Axis0 + j)); - if ((supported_buttons & button_mask) != 0) { - pad.buttons[pad.buttons_length].pressed = - (controller_state.ulButtonPressed & button_mask) != 0; - } - - pad.buttons_length++; - break; - } - } - - SetGamepadButton(&pad, controller_state, supported_buttons, - vr::k_EButton_A); - SetGamepadButton(&pad, controller_state, supported_buttons, - vr::k_EButton_Grip); - SetGamepadButton(&pad, controller_state, supported_buttons, - vr::k_EButton_ApplicationMenu); - SetGamepadButton(&pad, controller_state, supported_buttons, - vr::k_EButton_DPad_Left); - SetGamepadButton(&pad, controller_state, supported_buttons, - vr::k_EButton_DPad_Up); - SetGamepadButton(&pad, controller_state, supported_buttons, - vr::k_EButton_DPad_Right); - SetGamepadButton(&pad, controller_state, supported_buttons, - vr::k_EButton_DPad_Down); - } - - const vr::TrackedDevicePose_t& pose = tracked_devices_poses[i]; - if (pose.bPoseIsValid) { - const vr::HmdMatrix34_t& mat = pose.mDeviceToAbsoluteTracking; - gfx::Transform transform( - mat.m[0][0], mat.m[0][1], mat.m[0][2], mat.m[0][3], mat.m[1][0], - mat.m[1][1], mat.m[1][2], mat.m[1][3], mat.m[2][0], mat.m[2][1], - mat.m[2][2], mat.m[2][3], 0, 0, 0, 1); - - gfx::DecomposedTransform decomposed_transform; - gfx::DecomposeTransform(&decomposed_transform, transform); - - pad.pose.orientation.not_null = true; - pad.pose.orientation.x = decomposed_transform.quaternion.x(); - pad.pose.orientation.y = decomposed_transform.quaternion.y(); - pad.pose.orientation.z = decomposed_transform.quaternion.z(); - pad.pose.orientation.w = decomposed_transform.quaternion.w(); - - pad.pose.position.not_null = true; - pad.pose.position.x = decomposed_transform.translate[0]; - pad.pose.position.y = decomposed_transform.translate[1]; - pad.pose.position.z = decomposed_transform.translate[2]; - - pad.pose.angular_velocity.not_null = true; - pad.pose.angular_velocity.x = pose.vAngularVelocity.v[0]; - pad.pose.angular_velocity.y = pose.vAngularVelocity.v[1]; - pad.pose.angular_velocity.z = pose.vAngularVelocity.v[2]; - - pad.pose.linear_velocity.not_null = true; - pad.pose.linear_velocity.x = pose.vVelocity.v[0]; - pad.pose.linear_velocity.y = pose.vVelocity.v[1]; - pad.pose.linear_velocity.z = pose.vVelocity.v[2]; - } else { - pad.pose.orientation.not_null = false; - pad.pose.position.not_null = false; - pad.pose.angular_velocity.not_null = false; - pad.pose.linear_velocity.not_null = false; - } - } -} - -void OpenVRGamepadDataFetcher::UpdateGamepadData(OpenVRGamepadState data) { - base::AutoLock lock(lock_); - data_ = data; -} - -void OpenVRGamepadDataFetcher::PauseHint(bool paused) {} - -} // namespace device diff --git a/chromium/device/vr/openvr/openvr_gamepad_data_fetcher.h b/chromium/device/vr/openvr/openvr_gamepad_data_fetcher.h deleted file mode 100644 index 4df8cb612b1..00000000000 --- a/chromium/device/vr/openvr/openvr_gamepad_data_fetcher.h +++ /dev/null @@ -1,72 +0,0 @@ -// 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. - -#ifndef DEVICE_VR_OPENVR_GAMEPAD_DATA_FETCHER_H_ -#define DEVICE_VR_OPENVR_GAMEPAD_DATA_FETCHER_H_ - -#include "device/gamepad/gamepad_data_fetcher.h" -#include "third_party/openvr/src/headers/openvr.h" - -namespace device { - -class OpenVRGamepadDataFetcher; - -class OpenVRGamepadDataProvider { - public: - virtual void RegisterDataFetcher(OpenVRGamepadDataFetcher*) = 0; -}; - -struct OpenVRGamepadState { - int32_t axis_type[vr::k_unMaxTrackedDeviceCount] - [vr::k_unControllerStateAxisCount]; - int32_t ButtonMaskFromId[vr::k_unMaxTrackedDeviceCount] - [vr::k_unControllerStateAxisCount]; - - vr::TrackedDevicePose_t tracked_devices_poses[vr::k_unMaxTrackedDeviceCount]; - vr::ETrackedDeviceClass device_class[vr::k_unMaxTrackedDeviceCount]; - vr::VRControllerState_t controller_state[vr::k_unMaxTrackedDeviceCount]; - vr::ETrackedControllerRole hand[vr::k_unMaxTrackedDeviceCount]; - uint64_t supported_buttons[vr::k_unMaxTrackedDeviceCount]; - - bool have_controller_state[vr::k_unMaxTrackedDeviceCount] = {}; -}; - -class OpenVRGamepadDataFetcher : public GamepadDataFetcher { - public: - class Factory : public GamepadDataFetcherFactory { - public: - Factory(unsigned int display_id, OpenVRGamepadDataProvider*); - ~Factory() override; - std::unique_ptr<GamepadDataFetcher> CreateDataFetcher() override; - GamepadSource source() override; - - private: - unsigned int display_id_; - OpenVRGamepadDataProvider* provider_; - }; - - OpenVRGamepadDataFetcher(unsigned int display_id, OpenVRGamepadDataProvider*); - ~OpenVRGamepadDataFetcher() override; - - GamepadSource source() override; - - void GetGamepadData(bool devices_changed_hint) override; - void PauseHint(bool paused) override; - void OnAddedToProvider() override; - - void UpdateGamepadData(OpenVRGamepadState); // Called on UI thread. - - private: - unsigned int display_id_; - - // Protects access to data_, which is read/written on different threads. - base::Lock lock_; - - OpenVRGamepadState data_; - - DISALLOW_COPY_AND_ASSIGN(OpenVRGamepadDataFetcher); -}; - -} // namespace device -#endif // DEVICE_VR_OPENVR_GAMEPAD_DATA_FETCHER_H_ diff --git a/chromium/device/vr/openvr/openvr_gamepad_helper.cc b/chromium/device/vr/openvr/openvr_gamepad_helper.cc new file mode 100644 index 00000000000..2dd4d2f07dd --- /dev/null +++ b/chromium/device/vr/openvr/openvr_gamepad_helper.cc @@ -0,0 +1,171 @@ +// 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 "device/vr/openvr/openvr_gamepad_helper.h" + +#include <memory> + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "device/gamepad/public/cpp/gamepads.h" +#include "device/vr/vr_device.h" +#include "third_party/openvr/src/headers/openvr.h" +#include "ui/gfx/transform.h" +#include "ui/gfx/transform_util.h" + +namespace device { + +namespace { + +mojom::XRGamepadButtonPtr GetGamepadButton( + const vr::VRControllerState_t& controller_state, + uint64_t supported_buttons, + vr::EVRButtonId button_id) { + uint64_t button_mask = vr::ButtonMaskFromId(button_id); + if ((supported_buttons & button_mask) != 0) { + auto ret = mojom::XRGamepadButton::New(); + bool button_pressed = (controller_state.ulButtonPressed & button_mask) != 0; + bool button_touched = (controller_state.ulButtonTouched & button_mask) != 0; + ret->touched = button_touched; + ret->pressed = button_pressed; + ret->value = button_pressed ? 1.0 : 0.0; + return ret; + } + + return nullptr; +} + +} // namespace + +mojom::XRGamepadDataPtr OpenVRGamepadHelper::GetGamepadData( + vr::IVRSystem* vr_system) { + mojom::XRGamepadDataPtr ret = mojom::XRGamepadData::New(); + + vr::TrackedDevicePose_t tracked_devices_poses[vr::k_unMaxTrackedDeviceCount]; + vr_system->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseSeated, 0.0f, + tracked_devices_poses, + vr::k_unMaxTrackedDeviceCount); + for (uint32_t i = 0; i < vr::k_unMaxTrackedDeviceCount; ++i) { + if (vr_system->GetTrackedDeviceClass(i) != + vr::TrackedDeviceClass_Controller) + continue; + + vr::VRControllerState_t controller_state; + bool have_state = vr_system->GetControllerState(i, &controller_state, + sizeof(controller_state)); + if (!have_state) + continue; + + auto gamepad = mojom::XRGamepad::New(); + gamepad->controller_id = i; + + vr::ETrackedControllerRole hand = + vr_system->GetControllerRoleForTrackedDeviceIndex(i); + switch (hand) { + case vr::TrackedControllerRole_Invalid: + gamepad->hand = device::mojom::XRHandedness::NONE; + break; + case vr::TrackedControllerRole_LeftHand: + gamepad->hand = device::mojom::XRHandedness::LEFT; + break; + case vr::TrackedControllerRole_RightHand: + gamepad->hand = device::mojom::XRHandedness::RIGHT; + break; + } + + uint64_t supported_buttons = vr_system->GetUint64TrackedDeviceProperty( + i, vr::Prop_SupportedButtons_Uint64); + for (uint32_t j = 0; j < vr::k_unControllerStateAxisCount; ++j) { + int32_t axis_type = vr_system->GetInt32TrackedDeviceProperty( + i, + static_cast<vr::TrackedDeviceProperty>(vr::Prop_Axis0Type_Int32 + j)); + switch (axis_type) { + case vr::k_eControllerAxis_Joystick: + case vr::k_eControllerAxis_TrackPad: { + gamepad->axes.push_back(controller_state.rAxis[j].x); + gamepad->axes.push_back(controller_state.rAxis[j].y); + auto button = GetGamepadButton( + controller_state, supported_buttons, + static_cast<vr::EVRButtonId>(vr::k_EButton_Axis0 + j)); + if (button) { + gamepad->buttons.push_back(std::move(button)); + } + } break; + case vr::k_eControllerAxis_Trigger: { + auto button = mojom::XRGamepadButton::New(); + button->value = controller_state.rAxis[j].x; + uint64_t button_mask = vr::ButtonMaskFromId( + static_cast<vr::EVRButtonId>(vr::k_EButton_Axis0 + j)); + if ((supported_buttons & button_mask) != 0) { + button->pressed = + (controller_state.ulButtonPressed & button_mask) != 0; + } + gamepad->buttons.push_back(std::move(button)); + } break; + } + } + + auto button = + GetGamepadButton(controller_state, supported_buttons, vr::k_EButton_A); + if (button) + gamepad->buttons.push_back(std::move(button)); + button = GetGamepadButton(controller_state, supported_buttons, + vr::k_EButton_Grip); + if (button) + gamepad->buttons.push_back(std::move(button)); + button = GetGamepadButton(controller_state, supported_buttons, + vr::k_EButton_ApplicationMenu); + if (button) + gamepad->buttons.push_back(std::move(button)); + button = GetGamepadButton(controller_state, supported_buttons, + vr::k_EButton_DPad_Left); + if (button) + gamepad->buttons.push_back(std::move(button)); + button = GetGamepadButton(controller_state, supported_buttons, + vr::k_EButton_DPad_Up); + if (button) + gamepad->buttons.push_back(std::move(button)); + button = GetGamepadButton(controller_state, supported_buttons, + vr::k_EButton_DPad_Right); + if (button) + gamepad->buttons.push_back(std::move(button)); + button = GetGamepadButton(controller_state, supported_buttons, + vr::k_EButton_DPad_Down); + if (button) + gamepad->buttons.push_back(std::move(button)); + + const vr::TrackedDevicePose_t& pose = tracked_devices_poses[i]; + if (pose.bPoseIsValid) { + const vr::HmdMatrix34_t& mat = pose.mDeviceToAbsoluteTracking; + gfx::Transform transform( + mat.m[0][0], mat.m[0][1], mat.m[0][2], mat.m[0][3], mat.m[1][0], + mat.m[1][1], mat.m[1][2], mat.m[1][3], mat.m[2][0], mat.m[2][1], + mat.m[2][2], mat.m[2][3], 0, 0, 0, 1); + + gfx::DecomposedTransform src_pose; + gfx::DecomposeTransform(&src_pose, transform); + auto dst_pose = mojom::VRPose::New(); + + dst_pose->orientation = std::vector<float>( + {src_pose.quaternion.x(), src_pose.quaternion.y(), + src_pose.quaternion.z(), src_pose.quaternion.w()}); + dst_pose->position = + std::vector<float>({src_pose.translate[0], src_pose.translate[1], + src_pose.translate[2]}); + dst_pose->angularVelocity = std::vector<float>( + {pose.vAngularVelocity.v[0], pose.vAngularVelocity.v[1], + pose.vAngularVelocity.v[2]}); + dst_pose->linearVelocity = std::vector<float>( + {pose.vVelocity.v[0], pose.vVelocity.v[1], pose.vVelocity.v[2]}); + + gamepad->pose = std::move(dst_pose); + } + + ret->gamepads.push_back(std::move(gamepad)); + } + + return ret; +} + +} // namespace device diff --git a/chromium/device/vr/openvr/openvr_gamepad_helper.h b/chromium/device/vr/openvr/openvr_gamepad_helper.h new file mode 100644 index 00000000000..3bc7879de6a --- /dev/null +++ b/chromium/device/vr/openvr/openvr_gamepad_helper.h @@ -0,0 +1,19 @@ +// 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. + +#ifndef DEVICE_VR_OPENVR_OPENVR_GAMEPAD_HELPER_H_ +#define DEVICE_VR_OPENVR_OPENVR_GAMEPAD_HELPER_H_ + +#include "device/vr/public/mojom/isolated_xr_service.mojom.h" +#include "third_party/openvr/src/headers/openvr.h" + +namespace device { + +class OpenVRGamepadHelper { + public: + static mojom::XRGamepadDataPtr GetGamepadData(vr::IVRSystem* system); +}; + +} // namespace device +#endif // DEVICE_VR_OPENVR_OPENVR_GAMEPAD_HELPER_H_ diff --git a/chromium/device/vr/openvr/openvr_render_loop.cc b/chromium/device/vr/openvr/openvr_render_loop.cc index fa37e4db58e..2fae5df382d 100644 --- a/chromium/device/vr/openvr/openvr_render_loop.cc +++ b/chromium/device/vr/openvr/openvr_render_loop.cc @@ -6,7 +6,7 @@ #include "base/metrics/histogram_functions.h" #include "device/vr/openvr/openvr_api_wrapper.h" -#include "device/vr/openvr/openvr_gamepad_data_fetcher.h" +#include "device/vr/openvr/openvr_gamepad_helper.h" #include "device/vr/openvr/openvr_type_converters.h" #include "ui/gfx/geometry/angle_conversions.h" #include "ui/gfx/transform.h" @@ -45,12 +45,12 @@ gfx::Transform HmdMatrix34ToTransform(const vr::HmdMatrix34_t& mat) { } // namespace -OpenVRRenderLoop::OpenVRRenderLoop( - base::RepeatingCallback<void(OpenVRGamepadState)> update_gamepad) +OpenVRRenderLoop::OpenVRRenderLoop() : base::Thread("OpenVRRenderLoop"), main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()), - update_gamepad_(std::move(update_gamepad)), - binding_(this), + presentation_binding_(this), + frame_data_binding_(this), + gamepad_provider_(this), weak_ptr_factory_(this) { DCHECK(main_thread_task_runner_); } @@ -148,7 +148,15 @@ void OpenVRRenderLoop::SubmitFrameWithTextureHandle( void OpenVRRenderLoop::CleanUp() { submit_client_ = nullptr; - binding_.Close(); + presentation_binding_.Close(); + frame_data_binding_.Close(); + gamepad_provider_.Close(); +} + +void OpenVRRenderLoop::RequestGamepadProvider( + mojom::IsolatedXRGamepadProviderRequest request) { + gamepad_provider_.Close(); + gamepad_provider_.Bind(std::move(request)); } void OpenVRRenderLoop::UpdateLayerBounds(int16_t frame_id, @@ -166,18 +174,18 @@ void OpenVRRenderLoop::UpdateLayerBounds(int16_t frame_id, void OpenVRRenderLoop::RequestSession( base::OnceCallback<void()> on_presentation_ended, - mojom::XRDeviceRuntimeSessionOptionsPtr options, + mojom::XRRuntimeSessionOptionsPtr options, RequestSessionCallback callback) { DCHECK(options->immersive); - binding_.Close(); + presentation_binding_.Close(); + frame_data_binding_.Close(); if (!openvr_) { openvr_ = std::make_unique<OpenVRWrapper>(true); if (!openvr_->IsInitialized()) { openvr_ = nullptr; main_thread_task_runner_->PostTask( - FROM_HERE, base::BindOnce(std::move(callback), false, nullptr, - nullptr, nullptr)); + FROM_HERE, base::BindOnce(std::move(callback), false, nullptr)); return; } @@ -193,20 +201,22 @@ void OpenVRRenderLoop::RequestSession( !texture_helper_.EnsureInitialized()) { openvr_ = nullptr; main_thread_task_runner_->PostTask( - FROM_HERE, - base::BindOnce(std::move(callback), false, nullptr, nullptr, nullptr)); + FROM_HERE, base::BindOnce(std::move(callback), false, nullptr)); return; } #endif DCHECK(!on_presentation_ended_); on_presentation_ended_ = std::move(on_presentation_ended); - device::mojom::VRPresentationProviderPtr provider; - binding_.Bind(mojo::MakeRequest(&provider)); - device::mojom::VRDisplayFrameTransportOptionsPtr transport_options = - device::mojom::VRDisplayFrameTransportOptions::New(); + device::mojom::XRPresentationProviderPtr presentation_provider; + device::mojom::XRFrameDataProviderPtr frame_data_provider; + presentation_binding_.Bind(mojo::MakeRequest(&presentation_provider)); + frame_data_binding_.Bind(mojo::MakeRequest(&frame_data_provider)); + + device::mojom::XRPresentationTransportOptionsPtr transport_options = + device::mojom::XRPresentationTransportOptions::New(); transport_options->transport_method = - device::mojom::VRDisplayFrameTransportMethod::SUBMIT_AS_TEXTURE_HANDLE; + device::mojom::XRPresentationTransportMethod::SUBMIT_AS_TEXTURE_HANDLE; // Only set boolean options that we need. Default is false, and we should be // able to safely ignore ones that our implementation doesn't care about. transport_options->wait_for_transfer_notification = true; @@ -220,11 +230,17 @@ void OpenVRRenderLoop::RequestSession( input_active_state.controller_role = vr::TrackedControllerRole_Invalid; } + auto submit_frame_sink = device::mojom::XRPresentationConnection::New(); + submit_frame_sink->provider = presentation_provider.PassInterface(); + submit_frame_sink->client_request = mojo::MakeRequest(&submit_client_); + submit_frame_sink->transport_options = std::move(transport_options); + + auto session = device::mojom::XRSession::New(); + session->data_provider = frame_data_provider.PassInterface(); + session->submit_frame_sink = std::move(submit_frame_sink); + main_thread_task_runner_->PostTask( - FROM_HERE, - base::BindOnce(std::move(callback), true, - mojo::MakeRequest(&submit_client_), - provider.PassInterface(), std::move(transport_options))); + FROM_HERE, base::BindOnce(std::move(callback), true, std::move(session))); is_presenting_ = true; openvr_->GetCompositor()->SuspendRendering(false); @@ -247,7 +263,8 @@ void OpenVRRenderLoop::RequestSession( void OpenVRRenderLoop::ExitPresent() { is_presenting_ = false; - binding_.Close(); + presentation_binding_.Close(); + frame_data_binding_.Close(); submit_client_ = nullptr; if (openvr_) openvr_->GetCompositor()->SuspendRendering(true); @@ -266,37 +283,29 @@ void OpenVRRenderLoop::ExitPresent() { } void OpenVRRenderLoop::UpdateControllerState() { - OpenVRGamepadState state = {}; + if (!gamepad_callback_) { + // Nobody is listening to updates, so bail early. + return; + } - if (openvr_) { - vr::IVRSystem* vr_system = openvr_->GetSystem(); - vr_system->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseSeated, 0.0f, - state.tracked_devices_poses, - vr::k_unMaxTrackedDeviceCount); - - for (int i = 0; i < vr::k_unMaxTrackedDeviceCount; ++i) { - state.device_class[i] = vr_system->GetTrackedDeviceClass(i); - if (state.device_class[i] == vr::TrackedDeviceClass_Controller) { - state.have_controller_state[i] = vr_system->GetControllerState( - i, &(state.controller_state[i]), sizeof(state.controller_state[i])); - if (state.have_controller_state[i]) { - state.hand[i] = vr_system->GetControllerRoleForTrackedDeviceIndex(i); - state.supported_buttons[i] = - vr_system->GetUint64TrackedDeviceProperty( - i, vr::Prop_SupportedButtons_Uint64); - - for (int j = 0; j < vr::k_unControllerStateAxisCount; ++j) { - state.axis_type[i][j] = vr_system->GetInt32TrackedDeviceProperty( - i, static_cast<vr::TrackedDeviceProperty>( - vr::Prop_Axis0Type_Int32 + j)); - } - } - } - } + if (!openvr_) { + std::move(gamepad_callback_).Run(nullptr); + return; } - main_thread_task_runner_->PostTask(FROM_HERE, - base::BindOnce(update_gamepad_, state)); + std::move(gamepad_callback_) + .Run(OpenVRGamepadHelper::GetGamepadData(openvr_->GetSystem())); +} + +void OpenVRRenderLoop::RequestUpdate( + mojom::IsolatedXRGamepadProvider::RequestUpdateCallback callback) { + // Save the callback to resolve next time we update (typically on vsync). + gamepad_callback_ = std::move(callback); + + // If we aren't presenting, reply now saying that we have no controllers. + if (!is_presenting_) { + UpdateControllerState(); + } } mojom::VRPosePtr OpenVRRenderLoop::GetPose() { @@ -325,7 +334,7 @@ base::WeakPtr<OpenVRRenderLoop> OpenVRRenderLoop::GetWeakPtr() { } void OpenVRRenderLoop::GetFrameData( - mojom::VRPresentationProvider::GetFrameDataCallback callback) { + mojom::XRFrameDataProvider::GetFrameDataCallback callback) { DCHECK(is_presenting_); if (has_outstanding_frame_) { diff --git a/chromium/device/vr/openvr/openvr_render_loop.h b/chromium/device/vr/openvr/openvr_render_loop.h index df7fdc35e1f..bf0715d4abc 100644 --- a/chromium/device/vr/openvr/openvr_render_loop.h +++ b/chromium/device/vr/openvr/openvr_render_loop.h @@ -25,25 +25,24 @@ namespace device { class OpenVRWrapper; struct OpenVRGamepadState; -class OpenVRRenderLoop : public base::Thread, mojom::VRPresentationProvider { +class OpenVRRenderLoop : public base::Thread, + mojom::XRPresentationProvider, + mojom::XRFrameDataProvider, + mojom::IsolatedXRGamepadProvider { public: using RequestSessionCallback = - base::OnceCallback<void(bool result, - mojom::VRSubmitFrameClientRequest, - mojom::VRPresentationProviderPtrInfo, - mojom::VRDisplayFrameTransportOptionsPtr)>; + base::OnceCallback<void(bool result, mojom::XRSessionPtr)>; - OpenVRRenderLoop( - base::RepeatingCallback<void(OpenVRGamepadState)> update_gamepad); + OpenVRRenderLoop(); ~OpenVRRenderLoop() override; void RequestSession(base::OnceCallback<void()> on_presentation_ended, - mojom::XRDeviceRuntimeSessionOptionsPtr options, + mojom::XRRuntimeSessionOptionsPtr options, RequestSessionCallback callback); void ExitPresent(); base::WeakPtr<OpenVRRenderLoop> GetWeakPtr(); - // VRPresentationProvider overrides: + // XRPresentationProvider overrides: void SubmitFrameMissing(int16_t frame_index, const gpu::SyncToken&) override; void SubmitFrame(int16_t frame_index, const gpu::MailboxHolder& mailbox, @@ -58,7 +57,9 @@ class OpenVRRenderLoop : public base::Thread, mojom::VRPresentationProvider { const gfx::RectF& right_bounds, const gfx::Size& source_size) override; void GetFrameData( - VRPresentationProvider::GetFrameDataCallback callback) override; + XRFrameDataProvider::GetFrameDataCallback callback) override; + + void RequestGamepadProvider(mojom::IsolatedXRGamepadProviderRequest request); private: // base::Thread overrides: @@ -68,6 +69,10 @@ class OpenVRRenderLoop : public base::Thread, mojom::VRPresentationProvider { void ClearPendingFrame(); void UpdateControllerState(); + // IsolatedXRGamepadProvider + void RequestUpdate(mojom::IsolatedXRGamepadProvider::RequestUpdateCallback + callback) override; + mojom::VRPosePtr GetPose(); std::vector<mojom::XRInputSourceStatePtr> GetInputState( vr::TrackedDevicePose_t* poses, @@ -94,11 +99,15 @@ class OpenVRRenderLoop : public base::Thread, mojom::VRPresentationProvider { gfx::RectF right_bounds_; gfx::Size source_size_; scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_; - mojom::VRSubmitFrameClientPtr submit_client_; + mojom::XRPresentationClientPtr submit_client_; base::RepeatingCallback<void(OpenVRGamepadState)> update_gamepad_; base::OnceCallback<void()> on_presentation_ended_; + mojom::IsolatedXRGamepadProvider::RequestUpdateCallback gamepad_callback_; std::unique_ptr<OpenVRWrapper> openvr_; - mojo::Binding<mojom::VRPresentationProvider> binding_; + mojo::Binding<mojom::XRPresentationProvider> presentation_binding_; + mojo::Binding<mojom::XRFrameDataProvider> frame_data_binding_; + mojo::Binding<mojom::IsolatedXRGamepadProvider> gamepad_provider_; + base::WeakPtrFactory<OpenVRRenderLoop> weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(OpenVRRenderLoop); diff --git a/chromium/device/vr/orientation/orientation_device.cc b/chromium/device/vr/orientation/orientation_device.cc index ee322ddddc6..0d7fb6947ba 100644 --- a/chromium/device/vr/orientation/orientation_device.cc +++ b/chromium/device/vr/orientation/orientation_device.cc @@ -23,11 +23,11 @@ using gfx::Vector3dF; namespace { static constexpr int kDefaultPumpFrequencyHz = 60; -mojom::VRDisplayInfoPtr CreateVRDisplayInfo(unsigned int id) { +mojom::VRDisplayInfoPtr CreateVRDisplayInfo(mojom::XRDeviceId id) { static const char DEVICE_NAME[] = "VR Orientation Device"; mojom::VRDisplayInfoPtr display_info = mojom::VRDisplayInfo::New(); - display_info->index = id; + display_info->id = id; display_info->displayName = DEVICE_NAME; display_info->capabilities = mojom::VRDisplayCapabilities::New(); display_info->capabilities->hasPosition = false; @@ -52,7 +52,7 @@ display::Display::Rotation GetRotation() { VROrientationDevice::VROrientationDevice( mojom::SensorProviderPtr* sensor_provider, base::OnceClosure ready_callback) - : VRDeviceBase(VRDeviceId::ORIENTATION_DEVICE_ID), + : VRDeviceBase(mojom::XRDeviceId::ORIENTATION_DEVICE_ID), ready_callback_(std::move(ready_callback)), binding_(this) { (*sensor_provider) @@ -139,16 +139,16 @@ void VROrientationDevice::HandleSensorError() { } void VROrientationDevice::RequestSession( - mojom::XRDeviceRuntimeSessionOptionsPtr options, + mojom::XRRuntimeSessionOptionsPtr options, mojom::XRRuntime::RequestSessionCallback callback) { DCHECK(!options->immersive); - // TODO(offenwanger): Perform a check to see if sensors are available when - // RequestSession is called for non-immersive sessions. - std::move(callback).Run(nullptr, nullptr); + // TODO(http://crbug.com/695937): Perform a check to see if sensors are + // available when RequestSession is called for non-immersive sessions. + ReturnNonImmersiveSession(std::move(callback)); } void VROrientationDevice::OnMagicWindowFrameDataRequest( - mojom::VRMagicWindowProvider::GetFrameDataCallback callback) { + mojom::XRFrameDataProvider::GetFrameDataCallback callback) { mojom::VRPosePtr pose = mojom::VRPose::New(); pose->orientation.emplace(4); diff --git a/chromium/device/vr/orientation/orientation_device.h b/chromium/device/vr/orientation/orientation_device.h index f9f3b19a41e..d6f3c329142 100644 --- a/chromium/device/vr/orientation/orientation_device.h +++ b/chromium/device/vr/orientation/orientation_device.h @@ -44,12 +44,12 @@ class DEVICE_VR_EXPORT VROrientationDevice : public VRDeviceBase, // VRDevice void RequestSession( - mojom::XRDeviceRuntimeSessionOptionsPtr options, + mojom::XRRuntimeSessionOptionsPtr options, mojom::XRRuntime::RequestSessionCallback callback) override; // VRDeviceBase void OnMagicWindowFrameDataRequest( - mojom::VRMagicWindowProvider::GetFrameDataCallback callback) override; + mojom::XRFrameDataProvider::GetFrameDataCallback callback) override; // Indicates whether the device was able to connect to orientation events. bool IsAvailable() const { return available_; } diff --git a/chromium/device/vr/orientation/orientation_device_provider.cc b/chromium/device/vr/orientation/orientation_device_provider.cc index 7f3271569be..5cdf236eb3d 100644 --- a/chromium/device/vr/orientation/orientation_device_provider.cc +++ b/chromium/device/vr/orientation/orientation_device_provider.cc @@ -21,10 +21,10 @@ VROrientationDeviceProvider::VROrientationDeviceProvider( VROrientationDeviceProvider::~VROrientationDeviceProvider() = default; void VROrientationDeviceProvider::Initialize( - base::RepeatingCallback<void(unsigned int, + base::RepeatingCallback<void(mojom::XRDeviceId, mojom::VRDisplayInfoPtr, mojom::XRRuntimePtr)> add_device_callback, - base::RepeatingCallback<void(unsigned int)> remove_device_callback, + base::RepeatingCallback<void(mojom::XRDeviceId)> remove_device_callback, base::OnceClosure initialization_complete) { if (device_ && device_->IsAvailable()) { add_device_callback.Run(device_->GetId(), device_->GetVRDisplayInfo(), diff --git a/chromium/device/vr/orientation/orientation_device_provider.h b/chromium/device/vr/orientation/orientation_device_provider.h index d7772325430..a260c5ba38c 100644 --- a/chromium/device/vr/orientation/orientation_device_provider.h +++ b/chromium/device/vr/orientation/orientation_device_provider.h @@ -24,10 +24,10 @@ class DEVICE_VR_EXPORT VROrientationDeviceProvider : public VRDeviceProvider { ~VROrientationDeviceProvider() override; void Initialize( - base::RepeatingCallback<void(unsigned int, + base::RepeatingCallback<void(mojom::XRDeviceId, mojom::VRDisplayInfoPtr, mojom::XRRuntimePtr)> add_device_callback, - base::RepeatingCallback<void(unsigned int)> remove_device_callback, + base::RepeatingCallback<void(mojom::XRDeviceId)> remove_device_callback, base::OnceClosure initialization_complete) override; bool Initialized() override; @@ -42,7 +42,7 @@ class DEVICE_VR_EXPORT VROrientationDeviceProvider : public VRDeviceProvider { std::unique_ptr<VROrientationDevice> device_; base::RepeatingCallback< - void(unsigned int, mojom::VRDisplayInfoPtr, mojom::XRRuntimePtr)> + void(mojom::XRDeviceId, mojom::VRDisplayInfoPtr, mojom::XRRuntimePtr)> add_device_callback_; base::OnceClosure initialized_callback_; diff --git a/chromium/device/vr/orientation/orientation_device_provider_unittest.cc b/chromium/device/vr/orientation/orientation_device_provider_unittest.cc index bccfdc37de1..2909230cf07 100644 --- a/chromium/device/vr/orientation/orientation_device_provider_unittest.cc +++ b/chromium/device/vr/orientation/orientation_device_provider_unittest.cc @@ -83,22 +83,26 @@ class VROrientationDeviceProviderTest : public testing::Test { return init_params; } - base::RepeatingCallback< - void(unsigned int, mojom::VRDisplayInfoPtr, mojom::XRRuntimePtr device)> + base::RepeatingCallback<void(device::mojom::XRDeviceId, + mojom::VRDisplayInfoPtr, + mojom::XRRuntimePtr device)> DeviceAndIdCallbackFailIfCalled() { - return base::BindRepeating([](unsigned int id, mojom::VRDisplayInfoPtr, + return base::BindRepeating([](device::mojom::XRDeviceId id, + mojom::VRDisplayInfoPtr, mojom::XRRuntimePtr device) { FAIL(); }); }; - base::RepeatingCallback<void(unsigned int)> DeviceIdCallbackFailIfCalled() { - return base::BindRepeating([](unsigned int id) { FAIL(); }); + base::RepeatingCallback<void(device::mojom::XRDeviceId)> + DeviceIdCallbackFailIfCalled() { + return base::BindRepeating([](device::mojom::XRDeviceId id) { FAIL(); }); }; - base::RepeatingCallback< - void(unsigned int, mojom::VRDisplayInfoPtr, mojom::XRRuntimePtr device)> + base::RepeatingCallback<void(device::mojom::XRDeviceId, + mojom::VRDisplayInfoPtr, + mojom::XRRuntimePtr device)> DeviceAndIdCallbackMustBeCalled(base::RunLoop* loop) { return base::BindRepeating( - [](base::OnceClosure quit_closure, unsigned int id, + [](base::OnceClosure quit_closure, device::mojom::XRDeviceId id, mojom::VRDisplayInfoPtr info, mojom::XRRuntimePtr device) { ASSERT_TRUE(device); ASSERT_TRUE(info); @@ -107,10 +111,10 @@ class VROrientationDeviceProviderTest : public testing::Test { loop->QuitClosure()); }; - base::RepeatingCallback<void(unsigned int)> DeviceIdCallbackMustBeCalled( - base::RunLoop* loop) { + base::RepeatingCallback<void(device::mojom::XRDeviceId)> + DeviceIdCallbackMustBeCalled(base::RunLoop* loop) { return base::BindRepeating( - [](base::OnceClosure quit_closure, unsigned int id) { + [](base::OnceClosure quit_closure, device::mojom::XRDeviceId id) { std::move(quit_closure).Run(); }, loop->QuitClosure()); diff --git a/chromium/device/vr/public/mojom/BUILD.gn b/chromium/device/vr/public/mojom/BUILD.gn index 06cc4f2320d..a66e48534df 100644 --- a/chromium/device/vr/public/mojom/BUILD.gn +++ b/chromium/device/vr/public/mojom/BUILD.gn @@ -14,6 +14,7 @@ mojom_component("mojom") { ] public_deps = [ + "//device/gamepad/public/mojom", "//gpu/ipc/common:interfaces", "//mojo/public/mojom/base", "//ui/display/mojo:interfaces", diff --git a/chromium/device/vr/public/mojom/README.md b/chromium/device/vr/public/mojom/README.md index 273b0d5e2da..ad481a1b3e8 100644 --- a/chromium/device/vr/public/mojom/README.md +++ b/chromium/device/vr/public/mojom/README.md @@ -6,12 +6,31 @@ Some XRRuntimes must live in the browser process, while others must not live in the browser process. The ones that cannot live in the browser, are hosted in a service. +# Supported Session types + +## Magic-window: +Magic window sessions are requested by sites that request poses, but render +through the normal Chrome compositor pipeline. +It serves as a basic mode that requires only some way to get orientation poses. + +## Immersive: +Immersive sessions are where the site wishes to request poses, then render +content back to a display other than chrome. The common case for this is Head +Mounted Displays (HMD), like Vive, Oculus, or Daydream. + +## Environment Integration +This type of session allows for environment integration by providing functions +that allow the site to query the environment, such as HitTest. A Environment +Integration session may also supply data in addition to the pose, such as a +camera frame. + # Renderer <-> Browser interfaces (defined in vr_service.mojom) VRService - lives in the browser process, corresponds to a single frame. Root object to obtain other XR objects. -VRDisplayHost - lives in the browser process. Allows a client to start a -session (either immersive/exclusive/presenting or magic window). +XRDevice - lives in the browser process, implemented as XRDeviceImpl. Allows a +client to start a session (either immersive/exclusive/presenting or +non-immersive). VRServiceClient - lives in the renderer process. Is notified when VRDisplays are connected. @@ -24,22 +43,22 @@ These interfaces allow communication betwee an XRRuntime and the renderer process. They may live in the browser process or may live in the isolated service. -## Presentation-related: -Presentation is exclusive access to a headset where a site may display -a stereo view to the user. -VRPresentationProvider - lives in the XRDevice process. Implements the details -for a presentation session, such as submitting frames to the underlying VR API. +## Data related: +All sessions need to be able to get data from a XR device. -VRSubmitFrameClient - lives in the renderer process. Is notified when various -rendering events occur, so it can reclaim/reuse textures. +XRFrameDataProvider - lives in the XRDevice process. Provides a way to obtain +poses and other forms of data needed to render frames. -## Magic-window related: -Magic window is a mode where a site may request poses, but renders through the -normal Chrome compositor pipeline. +## Presentation related: +Presentation is exclusive access to a device, where the experience takes over +the device's display, such as presenting a stereo view in an HMD. -VRMagicWindowProvider - lives in the XRDevice process. Provides a way to obtain -poses. +XRPresentationProvider - lives in the XRDevice process. Implements the details +for a presentation session, such as submitting frames to the underlying VR API. + +XRPresentationClient - lives in the renderer process. Is notified when various +rendering events occur, so it can reclaim/reuse textures. # Browser <-> Device interfaces (defined in isolated_xr_service.mojom) The XRDevice process may be the browser process or an isolated service for @@ -55,3 +74,11 @@ pause or stop a session (MagicWindow or Presentation). XRRuntimeEventListener - Lives in the browser process. Exposes runtime events to the browser. + +# Browser <-> XRInput interfaces (defined in isolated_xr_service.mojom) +IsolatedXRGamepadProvider and IsolatedXRGamepadProviderFactory - Live in the +XRInput process, and allow GamepadDataFetchers living in the browser process +to expose data from gamepads that cannot be queried from the browser process. + +The XRInput process may be the browser process or a separate process depending +on the platform. diff --git a/chromium/device/vr/public/mojom/isolated_xr_service.mojom b/chromium/device/vr/public/mojom/isolated_xr_service.mojom index a1bcacda7c0..035c4a16a26 100644 --- a/chromium/device/vr/public/mojom/isolated_xr_service.mojom +++ b/chromium/device/vr/public/mojom/isolated_xr_service.mojom @@ -6,10 +6,12 @@ module device.mojom; import "device/vr/public/mojom/vr_service.mojom"; -// The XRSessionController lives in the vr device service, and corresponds to a -// VRPresentationProvider or a VRMagicWindowProvider. The client is the browser -// process, which will pause or stop sessions depending events/state such as -// focus or other tabs requesting presentation. +const string kVrIsolatedServiceName = "xr_device_service"; + +// The XRSessionController lives in the xr runtime service, and corresponds to +// a set of the XRSession bindings. The client is the browser process, which +// will pause or stop sessions depending events/state such as focus or other +// tabs requesting immersive sessions. // Sessions are stopped by closing the mojo connection. interface XRSessionController { // A session may be paused temporarily for example when a non-presenting @@ -18,7 +20,7 @@ interface XRSessionController { SetFrameDataRestricted(bool restricted); }; -// The XRRuntimeEventListener lives in the vr device service, and allows the +// The XRRuntimeEventListener lives in the xr runtime service, and allows the // browser to listen to state changes about a device. interface XRRuntimeEventListener { // A device has changed its display information. @@ -35,7 +37,7 @@ interface XRRuntimeEventListener { OnExitPresent(); }; -struct XRDeviceRuntimeSessionOptions { +struct XRRuntimeSessionOptions { bool immersive; bool provide_passthrough_camera; @@ -56,24 +58,16 @@ struct XRDeviceRuntimeSessionOptions { // An XRRuntime may live in the browser process or a utility process. The // browser process is the client, and may in turn expose device information to -// render processes using vr_service interfaces, such as VRDisplayHost. +// render processes using vr_service interfaces, such as XRDevice. interface XRRuntime { - // Attempt to start a presentation session. Clients may submit graphics to be - // displayed in the headset. Called by the browser process, but the - // VRPresentationProvider may be passed to the renderer process to allow - // submitting graphics without going through an extra IPC hop through the - // browser process. - RequestSession(XRDeviceRuntimeSessionOptions options) => ( - device.mojom.XRPresentationConnection? connection, + // Attempt to start a session. Called by the browser process, but the result + // will probably be passed to the renderer process to allow getting data and + // possibly submitting graphics without going through an extra IPC hop through + // the browser process. + RequestSession(XRRuntimeSessionOptions options) => ( + XRSession? session, XRSessionController? controller); - // Attempt to start a "magic window" session. Magic window sessions allow - // Clients to obtain poses (device position and orientation), but rendering - // goes through the standard Chrome compositor. - RequestMagicWindowSession() => - (device.mojom.VRMagicWindowProvider? session, - device.mojom.XRSessionController? controller); - // The browser may register for changes to a device. Initial VRDisplayInfo // will immediately be returned to the listener to prevent races. ListenToDeviceChanges(XRRuntimeEventListener listener) => @@ -81,3 +75,93 @@ interface XRRuntime { SetListeningForActivate(bool listen_for_activation); }; + +// Represents the state of a single button or trigger. +struct XRGamepadButton { + bool pressed; // Is the button currently pressed from its default position? + bool touched; // Is the user in contact with a button (always true if pressed) + double value; // How far pressed is it, from 0 to 1? +}; + +// Represents the state of a single controller. +struct XRGamepad { + bool can_provide_orientation; // Is the controller capable of orientation? + bool can_provide_position; // Is the controller capable of position? + array<double> axes; + array<XRGamepadButton> buttons; + + // The position/orientation of a controller, and its velocity and acceleration + // if available. Members inside this may be null if not available currently. + VRPose? pose; + + // Left/Right handed controller, or none if unknown. + XRHandedness hand; + + // A unique (per device_id) id that allows controllers to be tracked between + // updates. Useful to identify controllers as they are added/removed. + uint32 controller_id; +}; + +// Represents the state of a set of controllers driven by some runtime API. +struct XRGamepadData { + array<XRGamepad> gamepads; +}; + +// OpenVR and Oculus APIs can't run in the browser process, but the gamepad +// polling happens there. This interface allows gamepad polling to request +// data from out-of-process gamepad providers, at the cost of some extra IPC +// latency. IsolatedXRGamepadProvider is currently implemented in the XRRuntime +// process, and consumed by the gamepad polling thread in the browser process. +// It will move to live in a separate XRInput process in the future. +interface IsolatedXRGamepadProvider { + // Consumers should not call RequestUpdate until the previous request returns + // to avoid queuing up extra requests if polling and rendering are happening + // at different rates. If called while an outstanding request is queued, it + // returns immediately with null data. + // Returned data is null if we aren't currently getting data from the runtime. + RequestUpdate() => (XRGamepadData? data); +}; + +// Gamepad providers may come and go as pages request or stop requesting gamepad +// data. IsolatedXRGamepadProviderFactory allows GamepadDataFetchers to acquire +// new IsolatedXRGamepadProviders when needed. +// IsolatedXRGamepadProvider is consumed in the browser process. It is +// currently implemented in the XRRuntime process, but will move to a separate +// XRInput process. +interface IsolatedXRGamepadProviderFactory { + // Get the IsolatedXRGamepadProvider for a specific XR runtime API (Oculus, or + // OpenVR, which are currently the only two that are hosted outside of the + // browser process). + GetIsolatedXRGamepadProvider(IsolatedXRGamepadProvider& provider); +}; + +// Notify the browser process about a set of runtimes. The browser process +// implements this interface to be notified about runtime changes from the XR +// device service. +interface IsolatedXRRuntimeProviderClient { + // Called when runtimes are initially enumerated, or when devices are later + // attached and become available. + OnDeviceAdded(XRRuntime runtime, + IsolatedXRGamepadProviderFactory gamepad_factory, + device.mojom.VRDisplayInfo display_info); + + // Called when runtimes become unavailable - for example if the hardware is + // disconnected or the APIs notify us that they are shutting down. + // device_index corresponds to display_info->index for a previously added + // device. + OnDeviceRemoved(device.mojom.XRDeviceId device_index); + + // Called once after all the initial OnDeviceAdded calls are completed. + // This is a signal to the browser that it knows what hardware is available, + // and can unblock any callbacks/promises that WebXR APIs are blocked. + OnDevicesEnumerated(); +}; + +// Provides access to XRRuntimes. This is implemented in the XR device service, +// and consumed by the browser. +interface IsolatedXRRuntimeProvider { + // Register a client, and triggers OnDeviceAdded for all available runtimes, + // followed by OnDevicesEnumerated. + // Should only be called once. + RequestDevices(IsolatedXRRuntimeProviderClient client); +}; diff --git a/chromium/device/vr/public/mojom/vr_service.mojom b/chromium/device/vr/public/mojom/vr_service.mojom index 108c4291434..e752a0729b3 100644 --- a/chromium/device/vr/public/mojom/vr_service.mojom +++ b/chromium/device/vr/public/mojom/vr_service.mojom @@ -16,6 +16,19 @@ import "ui/gfx/mojo/transform.mojom"; // WebXR interfaces // +// TODO: Use EnableIf to only define values on platforms that have +// implementations. +enum XRDeviceId { + LAYOUT_TEST_DEVICE_ID = 0, // Fake device used by layout tests. + GVR_DEVICE_ID = 1, + OPENVR_DEVICE_ID = 2, + OCULUS_DEVICE_ID = 3, + ARCORE_DEVICE_ID = 4, + ORIENTATION_DEVICE_ID = 5, + FAKE_DEVICE_ID = 6, // Fake device used by unit tests. +}; + + enum XRHandedness { NONE = 0, LEFT = 1, @@ -41,17 +54,35 @@ struct XRSessionOptions { bool use_legacy_webvr_render_path; }; -// TODO(offenwanger) Rearrange these two interfaces to merge duplicate -// functionality. +// This structure contains all the mojo interfaces for different kinds of +// XRSession. The only interface required by all sessions is the +// XRFrameDataProvider. It must always be present. Other interfaces are set as +// apropriate based on the session creation options. (for example, an immersive +// session ought to have a XRPresentationConnection to submit the frames to the +// immersive environment). +// The XRSessionClient request must be fulfilled for the session to get +// information about the device it is connected to, such as focus and blur +// events, changes to view or stage parameters, or exit present calls initiated +// by the device. struct XRSession { - VRMagicWindowProvider? magic_window_provider; - XRPresentationConnection? connection; + XRFrameDataProvider data_provider; + // TODO(http://crbug.com/842025) Move where the client_request gets set to the + // device process then mark this as non-optional. + XRSessionClient&? client_request; + // TODO(http://crbug.com/842025) Move the information that is sent in display + // info to more sensible places so that this doesn't need to be sent here. + VRDisplayInfo display_info; + XRPresentationConnection? submit_frame_sink; + XREnvironmentIntegrationProvider? environment_provider; }; +// This structure contains the infomation and interfaces needed to create a two +// way connection between the renderer and a device to synchronize and submit +// frames to a sink outside of Chrome. struct XRPresentationConnection { - VRSubmitFrameClient& client_request; - VRPresentationProvider provider; - VRDisplayFrameTransportOptions transport_options; + XRPresentationProvider provider; + XRPresentationClient& client_request; + XRPresentationTransportOptions transport_options; }; struct XRInputSourceDescription { @@ -116,10 +147,8 @@ struct VRPose { }; struct XRRay { - // TODO(https://crbug.com/845293): use Point3F and Vector3F from - // ui/gfx/geometry and inline directly in requestHitTest(). - array<float, 3> origin; - array<float, 3> direction; + gfx.mojom.Point3F origin; + gfx.mojom.Vector3dF direction; }; struct XRHitResult { @@ -158,7 +187,7 @@ struct VRStageParameters { }; struct VRDisplayInfo { - uint32 index; + XRDeviceId id; string displayName; VRDisplayCapabilities capabilities; VRStageParameters? stageParameters; @@ -171,7 +200,7 @@ struct VRDisplayInfo { }; // Frame transport method from the Renderer's point of view. -enum VRDisplayFrameTransportMethod { +enum XRPresentationTransportMethod { NONE = 0, // Renderer should create a new texture handle (Windows) or @@ -185,10 +214,10 @@ enum VRDisplayFrameTransportMethod { DRAW_INTO_TEXTURE_MAILBOX = 3, }; -struct VRDisplayFrameTransportOptions { - VRDisplayFrameTransportMethod transport_method; +struct XRPresentationTransportOptions { + XRPresentationTransportMethod transport_method; - // Booleans indicating which of the VRSubmitFrameClient callbacks + // Booleans indicating which of the XRPresentationClient callbacks // are in use. Default is false, the device implementation should set // the ones to true that it needs and can ignore the rest. bool wait_for_transfer_notification; @@ -232,113 +261,86 @@ enum VRDisplayEventReason { UNMOUNTED = 3 }; -// TODO(shaobo.yan@intel.com) : Add comments to describe these interfaces about -// how to use and where they live. +// Interface for requesting XRDevice interfaces and registering for +// notifications that the XRDevice has changed interface VRService { - // TODO(shaobo.yan@intel.com, https://crbug.com/701027): Use a factory - // function which takes a VRServiceClient so we will never have a - // half-initialized VRService. - SetClient(VRServiceClient client) => (); - // Inform the service that the page is listening for vrdisplayactivate events. - // TODO(mthiesse): Move SetListeningForActivate onto VRDisplay. - SetListeningForActivate(bool listening); + // Returns the XRDevice interface which is used for creating XRSessions. This + // is not expected to be called once per renderer, unless the returned + // XRDevice is destroyed, then it might be called again to get another one. + RequestDevice() => (XRDevice? device); + // Optionally supply a VRServiceClient to listen for changes to the XRDevice. + // The last provided listener will have events called on it. + SetClient(VRServiceClient client); + + // WebVR 1.1 functionality compatibility method. To stop listening pass a null + // client. + SetListeningForActivate(VRDisplayClient? client); }; +// The interface for the renderer to listen to top level XR events, events that +// can be listened to and triggered without the renderer calling requestDevice. interface VRServiceClient { - OnDisplayConnected(VRDisplayHost display, VRDisplayClient& request, - VRDisplayInfo display_info); + // Signals changes to the available physical device runtimes. + OnDeviceChanged(); }; -// After submitting a frame, the VRPresentationProvider will notify the client -// about several stages of the render pipeline. This allows pipelining -// efficiency. Following VRPresentationProvider::Submit*, the submitted frame -// will be transferred (read from, perhaps copied to another texture), and then -// rendered (submitted to the underlying VR API). -// The client lives in the render process, implemented by VRDisplay. -// -// See VRDisplayFrameTransportConfiguration which configures which of these -// callbacks are in use. -interface VRSubmitFrameClient { - // The VRPresentationProvider calls this to indicate that the submitted frame - // has been transferred, so the backing data (mailbox or GpuMemoryBuffer) can - // be reused or discarded. Note that this is a convenience/optimization - // feature, not a security feature - if a site discards the data early we may - // drop a frame, but nothing will otherwise misbehave. - // When the frame wasn't successfully transferred, the client should create a - // new mailbox/GpuMemoryBuffer rather than reusing an existing one to recover - // for subsequent frames. - OnSubmitFrameTransferred(bool success); - - // The VRPresentationProvider calls this after the frame was handed off to the - // underlying VR API. This allows some pipelining of CPU/GPU work, while - // delaying expensive tasks for a subsequent frame until the current frame has - // completed. - OnSubmitFrameRendered(); - - // This callback provides a GpuFence corresponding to the previous frame's - // rendering completion, intended for use with a server wait issued before - // the following wait to prevent its rendering work from competing with - // the previous frame. - OnSubmitFrameGpuFence(gfx.mojom.GpuFenceHandle gpu_fence_handle); -}; - - -// Provides a communication channel from the renderer to the browser-side host -// of a (device/) VrDisplayImpl. -interface VRDisplayHost { - // Request to initialize a session in the browser process. The return value - // indicates if the session was successfully initialized or not. - // TODO(https://crbug.com/842025): Refactor VR device interfaces to better - // reflect WebXR. +// Supplies XRSessions and session support information. Implemented in the +// browser process, consumed in the renderer process. +interface XRDevice { + // Request to initialize a session in the browser process. If successful, the + // XRSession struct with the requisite interfaces will be returned. RequestSession( XRSessionOptions options, bool triggered_by_displayactive) => (XRSession? session); SupportsSession(XRSessionOptions options) => (bool supports_session); + // WebVR 1.1 functionality compatibility method. Returns VRDisplayInfo for an + // immersive session if immersive is supported. If (and only if) immersive is + // not supported, will return a nullptr. This call might cause device specific + // UI to appear. + GetImmersiveVRDisplayInfo() => (VRDisplayInfo? info); + ExitPresent(); }; -// Provides the necessary functionality for a non-presenting WebXR session to -// draw magic window content. +// Provides the necessary functionality for a WebXR session to get data for +// drawing frames. The kind of data it gets depends on what kind of session was +// requested. // This interface is hosted in the Browser process, but will move to a sandboxed // utility process on Windows. The render process communicates with it. -// For AR displays (VRDisplayCapabilities.can_provide_pass_through_images -// is true), clients can use GetFrameData to get images. -// TODO(836478): rename VRMagicWindowProvider to NonImmersiveWindowProvider or -// similar. -interface VRMagicWindowProvider { +interface XRFrameDataProvider { // frame_data is optional and will not be set if and only if the call fails // for some reason, such as device disconnection. GetFrameData() => (XRFrameData? frame_data); +}; - // Different devices can have different native orientations - 0 - // is the native orientation, and then increments of 90 degrees - // from there. +// Provides functionality for integrating environment information into an +// XRSession. For example, some AR sessions would implement hit test to allow +// developers to get the information about the world that its sensors supply. +interface XREnvironmentIntegrationProvider { + // Different devices can have different native orientations - 0 is the native + // orientation, and then increments of 90 degrees from there. Session geometry + // is needed by the device when integrating environment image data, i.e. + // camera feeds, into a session. UpdateSessionGeometry( gfx.mojom.Size frame_size, display.mojom.Rotation display_rotation); - // Performs a raycast into the magic window scene and returns a list of - // XRHitResults sorted from closest to furthest hit from the ray. Each - // hit result contains a hit_matrix containing the transform of the hit - // where the rotation represents the normal of the surface hit. + // Performs a raycast into the scene and returns a list of XRHitResults sorted + // from closest to furthest hit from the ray. Each hit result contains a + // hit_matrix containing the transform of the hit where the rotation + // represents the normal of the surface hit. // An empty result list means there were no hits. If a nullopt is returned, // there was an error. - // TODO(https://crbug.com/842025): have one "session" type, merging - // VRMagicWindowProvider and VRPresentationProvider because RequestHitTest - // makes sense for both types of sessions. RequestHitTest(XRRay ray) => (array<XRHitResult>? results); }; -// Provides the necessary functionality for a presenting WebVR page to draw -// frames for a VrDisplay. +// Provides the necessary functionality for sending frames to a headset. // This interface is hosted in the Browser process, but will move to a sandboxed // utility process on Windows. The render process communicates with it. -interface VRPresentationProvider { - // frame_data is optional and will not be set if and only if the call fails - // for some reason, such as device disconnection. - GetFrameData() => (XRFrameData? frame_data); - +interface XRPresentationProvider { + // This function tells the device which parts of the canvas should be rendered + // to which view. UpdateLayerBounds(int16 frame_id, gfx.mojom.RectF left_bounds, gfx.mojom.RectF right_bounds, gfx.mojom.Size source_size); @@ -346,31 +348,75 @@ interface VRPresentationProvider { // ensure that every GetFrameData has a matching Submit call. This happens for // WebXR if there were no drawing operations to the opaque framebuffer, and // for WebVR 1.1 if the application didn't call SubmitFrame. Usable with any - // VRDisplayFrameTransportMethod. This path does *not* call the + // XRPresentationTransportMethod. This path does *not* call the // SubmitFrameClient methods such as OnSubmitFrameTransferred. This is // intended to help separate frames while presenting, it may or may not // be called for the last animating frame when presentation ends. SubmitFrameMissing(int16 frame_id, gpu.mojom.SyncToken sync_token); - // VRDisplayFrameTransportMethod SUBMIT_AS_MAILBOX_HOLDER + // XRPresentationTransportMethod SUBMIT_AS_MAILBOX_HOLDER SubmitFrame(int16 frame_id, gpu.mojom.MailboxHolder mailbox_holder, mojo_base.mojom.TimeDelta time_waited); - // VRDisplayFrameTransportMethod SUBMIT_AS_TEXTURE_HANDLE + // XRPresentationTransportMethod SUBMIT_AS_TEXTURE_HANDLE // TODO(https://crbug.com/676224): Support preprocessing of mojom files, since // this is Windows only. SubmitFrameWithTextureHandle(int16 frameId, handle texture); - // VRDisplayFrameTransportMethod DRAW_INTO_TEXTURE_MAILBOX + // XRPresentationTransportMethod DRAW_INTO_TEXTURE_MAILBOX SubmitFrameDrawnIntoTexture(int16 frameId, gpu.mojom.SyncToken sync_token, mojo_base.mojom.TimeDelta time_waited); }; -interface VRDisplayClient { +// After submitting a frame, the XRPresentationProvider will notify the client +// about several stages of the render pipeline. This allows pipelining +// efficiency. Following XRPresentationProvider::Submit*, the submitted frame +// will be transferred (read from, perhaps copied to another texture), and then +// rendered (submitted to the underlying VR API). +// The client lives in the render process. +// +// See XRPresentationTransportOptions which configures which of these +// callbacks are in use. +interface XRPresentationClient { + // The XRPresentationProvider calls this to indicate that the submitted frame + // has been transferred, so the backing data (mailbox or GpuMemoryBuffer) can + // be reused or discarded. Note that this is a convenience/optimization + // feature, not a security feature - if a site discards the data early we may + // drop a frame, but nothing will otherwise misbehave. + // When the frame wasn't successfully transferred, the client should create a + // new mailbox/GpuMemoryBuffer rather than reusing an existing one to recover + // for subsequent frames. + OnSubmitFrameTransferred(bool success); + + // The XRPresentationProvider calls this after the frame was handed off to the + // underlying VR API. This allows some pipelining of CPU/GPU work, while + // delaying expensive tasks for a subsequent frame until the current frame has + // completed. + OnSubmitFrameRendered(); + + // This callback provides a GpuFence corresponding to the previous frame's + // rendering completion, intended for use with a server wait issued before + // the following wait to prevent its rendering work from competing with + // the previous frame. + OnSubmitFrameGpuFence(gfx.mojom.GpuFenceHandle gpu_fence_handle); +}; + +// Functions for pushing device information to the sessions. +interface XRSessionClient { OnChanged(VRDisplayInfo display); OnExitPresent(); OnBlur(); OnFocus(); +}; + +// Backwards compatibility events for WebVR 1.1. These are expected to not be +// used for WebXR. +interface VRDisplayClient { + // Inform the renderer that a headset has sent a signal indicating that the + // user has put it on. Returns an indicator of whether or not the page + // actually started a WebVR 1.1 presentation. OnActivate(VRDisplayEventReason reason) => (bool will_not_present); + // Inform the renderer that a headset has sent a signal indicating that the + // user stopped using a headset. OnDeactivate(VRDisplayEventReason reason); }; diff --git a/chromium/device/vr/vr_device.h b/chromium/device/vr/vr_device.h index aaa2294fa79..abc3dffe3b1 100644 --- a/chromium/device/vr/vr_device.h +++ b/chromium/device/vr/vr_device.h @@ -27,16 +27,6 @@ enum class VrViewerType { VIEWER_TYPE_COUNT, }; -// Hardcoded list of ids for each device type. -enum class VRDeviceId : unsigned int { - GVR_DEVICE_ID = 1, - OPENVR_DEVICE_ID = 2, - OCULUS_DEVICE_ID = 3, - ARCORE_DEVICE_ID = 4, - ORIENTATION_DEVICE_ID = 5, - FAKE_DEVICE_ID = 6, -}; - // These values are persisted to logs. Entries should not be renumbered and // numeric values should never be reused. enum class XrRuntimeAvailable { diff --git a/chromium/device/vr/vr_device_base.cc b/chromium/device/vr/vr_device_base.cc index 1c7703ad6d3..af19fab1000 100644 --- a/chromium/device/vr/vr_device_base.cc +++ b/chromium/device/vr/vr_device_base.cc @@ -9,12 +9,12 @@ namespace device { -VRDeviceBase::VRDeviceBase(VRDeviceId id) - : id_(static_cast<unsigned int>(id)), runtime_binding_(this) {} +VRDeviceBase::VRDeviceBase(mojom::XRDeviceId id) + : id_(id), runtime_binding_(this) {} VRDeviceBase::~VRDeviceBase() = default; -unsigned int VRDeviceBase::GetId() const { +mojom::XRDeviceId VRDeviceBase::GetId() const { return id_; } @@ -53,7 +53,7 @@ void VRDeviceBase::ListenToDeviceChanges( } void VRDeviceBase::GetFrameData( - mojom::VRMagicWindowProvider::GetFrameDataCallback callback) { + mojom::XRFrameDataProvider::GetFrameDataCallback callback) { if (!magic_window_enabled_) { std::move(callback).Run(nullptr); return; @@ -64,7 +64,7 @@ void VRDeviceBase::GetFrameData( void VRDeviceBase::SetVRDisplayInfo(mojom::VRDisplayInfoPtr display_info) { DCHECK(display_info); - DCHECK(display_info->index == id_); + DCHECK(display_info->id == id_); bool initialized = !!display_info_; display_info_ = std::move(display_info); @@ -95,7 +95,7 @@ bool VRDeviceBase::ShouldPauseTrackingWhenFrameDataRestricted() { void VRDeviceBase::OnListeningForActivate(bool listening) {} void VRDeviceBase::OnMagicWindowFrameDataRequest( - mojom::VRMagicWindowProvider::GetFrameDataCallback callback) { + mojom::XRFrameDataProvider::GetFrameDataCallback callback) { std::move(callback).Run(nullptr); } @@ -105,18 +105,31 @@ void VRDeviceBase::SetListeningForActivate(bool is_listening) { void VRDeviceBase::RequestHitTest( mojom::XRRayPtr ray, - mojom::VRMagicWindowProvider::RequestHitTestCallback callback) { + mojom::XREnvironmentIntegrationProvider::RequestHitTestCallback callback) { NOTREACHED() << "Unexpected call to a device without hit-test support"; std::move(callback).Run(base::nullopt); } -void VRDeviceBase::RequestMagicWindowSession( - mojom::XRRuntime::RequestMagicWindowSessionCallback callback) { - mojom::VRMagicWindowProviderPtr provider; +void VRDeviceBase::ReturnNonImmersiveSession( + mojom::XRRuntime::RequestSessionCallback callback) { + mojom::XRFrameDataProviderPtr data_provider; + mojom::XREnvironmentIntegrationProviderPtr environment_provider; mojom::XRSessionControllerPtr controller; - magic_window_sessions_.push_back(std::make_unique<VRDisplayImpl>( - this, mojo::MakeRequest(&provider), mojo::MakeRequest(&controller))); - std::move(callback).Run(std::move(provider), std::move(controller)); + magic_window_sessions_.push_back( + std::make_unique<VRDisplayImpl>(this, mojo::MakeRequest(&data_provider), + mojo::MakeRequest(&environment_provider), + mojo::MakeRequest(&controller))); + + auto session = mojom::XRSession::New(); + session->data_provider = data_provider.PassInterface(); + // TODO(http://crbug.com/876135) Not all sessions want the environment + // provider. This should be refactored to only be passed when requested. + session->environment_provider = environment_provider.PassInterface(); + if (display_info_) { + session->display_info = display_info_.Clone(); + } + + std::move(callback).Run(std::move(session), std::move(controller)); } void VRDeviceBase::EndMagicWindowSession(VRDisplayImpl* session) { diff --git a/chromium/device/vr/vr_device_base.h b/chromium/device/vr/vr_device_base.h index 04f6c5066d6..8d10ef56e65 100644 --- a/chromium/device/vr/vr_device_base.h +++ b/chromium/device/vr/vr_device_base.h @@ -22,7 +22,7 @@ class VRDisplayImpl; // TODO(mthiesse, crbug.com/769373): Remove DEVICE_VR_EXPORT. class DEVICE_VR_EXPORT VRDeviceBase : public mojom::XRRuntime { public: - explicit VRDeviceBase(VRDeviceId id); + explicit VRDeviceBase(mojom::XRDeviceId id); ~VRDeviceBase() override; // VRDevice Implementation @@ -31,13 +31,12 @@ class DEVICE_VR_EXPORT VRDeviceBase : public mojom::XRRuntime { mojom::XRRuntime::ListenToDeviceChangesCallback callback) final; void SetListeningForActivate(bool is_listening) override; - void GetFrameData( - mojom::VRMagicWindowProvider::GetFrameDataCallback callback); + void GetFrameData(mojom::XRFrameDataProvider::GetFrameDataCallback callback); virtual void RequestHitTest( mojom::XRRayPtr ray, - mojom::VRMagicWindowProvider::RequestHitTestCallback callback); - unsigned int GetId() const; + mojom::XREnvironmentIntegrationProvider::RequestHitTestCallback callback); + device::mojom::XRDeviceId GetId() const; bool HasExclusiveSession(); void EndMagicWindowSession(VRDisplayImpl* session); @@ -75,25 +74,23 @@ class DEVICE_VR_EXPORT VRDeviceBase : public mojom::XRRuntime { void OnActivate(mojom::VRDisplayEventReason reason, base::Callback<void(bool)> on_handled); + void ReturnNonImmersiveSession( + mojom::XRRuntime::RequestSessionCallback callback); + + mojom::VRDisplayInfoPtr display_info_; std::vector<std::unique_ptr<VRDisplayImpl>> magic_window_sessions_; private: // TODO(https://crbug.com/842227): Rename methods to HandleOnXXX virtual void OnListeningForActivate(bool listening); virtual void OnMagicWindowFrameDataRequest( - mojom::VRMagicWindowProvider::GetFrameDataCallback callback); - - // XRRuntime - void RequestMagicWindowSession( - mojom::XRRuntime::RequestMagicWindowSessionCallback callback) override; + mojom::XRFrameDataProvider::GetFrameDataCallback callback); mojom::XRRuntimeEventListenerPtr listener_; - mojom::VRDisplayInfoPtr display_info_; - bool presenting_ = false; - unsigned int id_; + device::mojom::XRDeviceId id_; bool magic_window_enabled_ = true; mojo::Binding<mojom::XRRuntime> runtime_binding_; diff --git a/chromium/device/vr/vr_device_base_unittest.cc b/chromium/device/vr/vr_device_base_unittest.cc index 5025d3ed0e3..318b292b971 100644 --- a/chromium/device/vr/vr_device_base_unittest.cc +++ b/chromium/device/vr/vr_device_base_unittest.cc @@ -11,8 +11,8 @@ #include "device/vr/public/mojom/vr_service.mojom.h" #include "device/vr/test/fake_vr_device.h" #include "device/vr/test/fake_vr_service_client.h" -#include "device/vr/test/mock_vr_display_impl.h" #include "device/vr/vr_device_base.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace device { @@ -21,7 +21,7 @@ namespace { class VRDeviceBaseForTesting : public VRDeviceBase { public: - VRDeviceBaseForTesting() : VRDeviceBase(VRDeviceId::FAKE_DEVICE_ID) {} + VRDeviceBaseForTesting() : VRDeviceBase(mojom::XRDeviceId::FAKE_DEVICE_ID) {} ~VRDeviceBaseForTesting() override = default; void SetVRDisplayInfoForTest(mojom::VRDisplayInfoPtr display_info) { @@ -35,7 +35,7 @@ class VRDeviceBaseForTesting : public VRDeviceBase { bool ListeningForActivate() { return listening_for_activate; } void RequestSession( - mojom::XRDeviceRuntimeSessionOptionsPtr options, + mojom::XRRuntimeSessionOptionsPtr options, mojom::XRRuntime::RequestSessionCallback callback) override {} private: @@ -95,14 +95,6 @@ class VRDeviceTest : public testing::Test { client_ = std::make_unique<FakeVRServiceClient>(mojo::MakeRequest(&proxy)); } - std::unique_ptr<MockVRDisplayImpl> MakeMockDisplay(VRDeviceBase* device) { - mojom::VRMagicWindowProviderPtr session; - mojom::XRSessionControllerPtr controller; - return std::make_unique<testing::NiceMock<MockVRDisplayImpl>>( - device, mojo::MakeRequest(&session), mojo::MakeRequest(&controller), - false); - } - std::unique_ptr<VRDeviceBaseForTesting> MakeVRDevice() { std::unique_ptr<VRDeviceBaseForTesting> device = std::make_unique<VRDeviceBaseForTesting>(); @@ -110,9 +102,9 @@ class VRDeviceTest : public testing::Test { return device; } - mojom::VRDisplayInfoPtr MakeVRDisplayInfo(unsigned int device_id) { + mojom::VRDisplayInfoPtr MakeVRDisplayInfo(mojom::XRDeviceId device_id) { mojom::VRDisplayInfoPtr display_info = mojom::VRDisplayInfo::New(); - display_info->index = device_id; + display_info->id = device_id; display_info->capabilities = mojom::VRDisplayCapabilities::New(); return display_info; } @@ -158,7 +150,8 @@ TEST_F(VRDeviceTest, DisplayActivateRegsitered) { } TEST_F(VRDeviceTest, NoMagicWindowPosesWhileBrowsing) { - auto device = std::make_unique<FakeVRDevice>(1); + auto device = + std::make_unique<FakeVRDevice>(static_cast<device::mojom::XRDeviceId>(1)); device->SetPose(mojom::VRPose::New()); device->GetFrameData(base::BindOnce( diff --git a/chromium/device/vr/vr_device_provider.h b/chromium/device/vr/vr_device_provider.h index 26732491088..27616caa4f4 100644 --- a/chromium/device/vr/vr_device_provider.h +++ b/chromium/device/vr/vr_device_provider.h @@ -19,10 +19,11 @@ class VRDeviceProvider { // If the VR API requires initialization that should happen here. virtual void Initialize( - base::RepeatingCallback<void(unsigned int, + base::RepeatingCallback<void(mojom::XRDeviceId id, mojom::VRDisplayInfoPtr, mojom::XRRuntimePtr)> add_device_callback, - base::RepeatingCallback<void(unsigned int)> remove_device_callback, + base::RepeatingCallback<void(mojom::XRDeviceId id)> + remove_device_callback, base::OnceClosure initialization_complete) = 0; // Returns true if initialization is complete. diff --git a/chromium/device/vr/vr_display_impl.cc b/chromium/device/vr/vr_display_impl.cc index b4e482b3de3..ce0c958e77e 100644 --- a/chromium/device/vr/vr_display_impl.cc +++ b/chromium/device/vr/vr_display_impl.cc @@ -17,14 +17,13 @@ namespace device { VRDisplayImpl::VRDisplayImpl( VRDeviceBase* device, - mojom::VRMagicWindowProviderRequest magic_window_request, + mojom::XRFrameDataProviderRequest magic_window_request, + mojom::XREnvironmentIntegrationProviderRequest environment_request, mojom::XRSessionControllerRequest session_request) - : magic_window_binding_(this), - session_controller_binding_(this), + : magic_window_binding_(this, std::move(magic_window_request)), + environment_binding_(this, std::move(environment_request)), + session_controller_binding_(this, std::move(session_request)), device_(device) { - magic_window_binding_.Bind(std::move(magic_window_request)); - session_controller_binding_.Bind(std::move(session_request)); - // Unretained is safe because the binding will close when we are destroyed, // so we won't receive any more callbacks after that. session_controller_binding_.set_connection_error_handler(base::BindOnce( @@ -35,7 +34,7 @@ VRDisplayImpl::~VRDisplayImpl() = default; // Gets frame data for sessions. void VRDisplayImpl::GetFrameData( - mojom::VRMagicWindowProvider::GetFrameDataCallback callback) { + mojom::XRFrameDataProvider::GetFrameDataCallback callback) { if (device_->HasExclusiveSession() || restrict_frame_data_) { std::move(callback).Run(nullptr); return; @@ -63,7 +62,7 @@ void VRDisplayImpl::UpdateSessionGeometry(const gfx::Size& frame_size, void VRDisplayImpl::RequestHitTest( mojom::XRRayPtr ray, - mojom::VRMagicWindowProvider::RequestHitTestCallback callback) { + mojom::XREnvironmentIntegrationProvider::RequestHitTestCallback callback) { if (restrict_frame_data_) { std::move(callback).Run(base::nullopt); return; diff --git a/chromium/device/vr/vr_display_impl.h b/chromium/device/vr/vr_display_impl.h index 0d0bd8fd080..219c9c8c611 100644 --- a/chromium/device/vr/vr_display_impl.h +++ b/chromium/device/vr/vr_display_impl.h @@ -20,15 +20,18 @@ namespace device { class VRDeviceBase; -// VR device process implementation of a VRMagicWindowProvider within a WebVR +// VR device process implementation of a XRFrameDataProvider within a WebVR // or WebXR site session. // VRDisplayImpl objects are owned by their respective XRRuntime instances. -// TODO(offenwanger): Rename this. -class DEVICE_VR_EXPORT VRDisplayImpl : public mojom::VRMagicWindowProvider, - public mojom::XRSessionController { +// TODO(http://crbug.com/842025): Rename this. +class DEVICE_VR_EXPORT VRDisplayImpl + : public mojom::XRFrameDataProvider, + public mojom::XREnvironmentIntegrationProvider, + public mojom::XRSessionController { public: VRDisplayImpl(VRDeviceBase* device, - mojom::VRMagicWindowProviderRequest, + mojom::XRFrameDataProviderRequest, + mojom::XREnvironmentIntegrationProviderRequest, mojom::XRSessionControllerRequest); ~VRDisplayImpl() override; @@ -39,7 +42,7 @@ class DEVICE_VR_EXPORT VRDisplayImpl : public mojom::VRMagicWindowProvider, // Accessible to tests. protected: - // mojom::VRMagicWindowProvider + // mojom::XRFrameDataProvider void GetFrameData(GetFrameDataCallback callback) override; void UpdateSessionGeometry(const gfx::Size& frame_size, display::Display::Rotation rotation) override; @@ -51,7 +54,8 @@ class DEVICE_VR_EXPORT VRDisplayImpl : public mojom::VRMagicWindowProvider, void OnMojoConnectionError(); - mojo::Binding<mojom::VRMagicWindowProvider> magic_window_binding_; + mojo::Binding<mojom::XRFrameDataProvider> magic_window_binding_; + mojo::Binding<mojom::XREnvironmentIntegrationProvider> environment_binding_; mojo::Binding<mojom::XRSessionController> session_controller_binding_; device::VRDeviceBase* device_; bool restrict_frame_data_ = true; diff --git a/chromium/device/vr/vr_display_impl_unittest.cc b/chromium/device/vr/vr_display_impl_unittest.cc index 6735be754f8..fc8ad17219b 100644 --- a/chromium/device/vr/vr_display_impl_unittest.cc +++ b/chromium/device/vr/vr_display_impl_unittest.cc @@ -21,17 +21,10 @@ class VRDisplayImplTest : public testing::Test { VRDisplayImplTest() {} ~VRDisplayImplTest() override {} void onDisplaySynced() {} - void onPresentComplete( - device::mojom::XRPresentationConnectionPtr connection, - mojom::XRSessionControllerPtr immersive_session_controller) { - is_request_presenting_success_ = connection ? true : false; - - immersive_session_controller_ = std::move(immersive_session_controller); - } protected: void SetUp() override { - device_ = std::make_unique<FakeVRDevice>(1); + device_ = std::make_unique<FakeVRDevice>(static_cast<mojom::XRDeviceId>(1)); device_->SetPose(mojom::VRPose::New()); mojom::VRServiceClientPtr proxy; client_ = std::make_unique<FakeVRServiceClient>(mojo::MakeRequest(&proxy)); @@ -39,9 +32,12 @@ class VRDisplayImplTest : public testing::Test { std::unique_ptr<VRDisplayImpl> MakeDisplay( mojom::XRSessionControllerPtr* controller) { - mojom::VRMagicWindowProviderPtr session; + mojom::XRFrameDataProviderPtr data_provider; + mojom::XREnvironmentIntegrationProviderPtr environment_provider; auto display = std::make_unique<VRDisplayImpl>( - device(), mojo::MakeRequest(&session), mojo::MakeRequest(controller)); + device(), mojo::MakeRequest(&data_provider), + mojo::MakeRequest(&environment_provider), + mojo::MakeRequest(controller)); static_cast<mojom::XRSessionController*>(display.get()) ->SetFrameDataRestricted(true); return display; @@ -49,14 +45,14 @@ class VRDisplayImplTest : public testing::Test { void RequestSession(VRDisplayImpl* display_impl) { device_->RequestSession( - mojom::XRDeviceRuntimeSessionOptionsPtr(), - base::BindOnce(&VRDisplayImplTest::onPresentComplete, - base::Unretained(this))); + mojom::XRRuntimeSessionOptionsPtr(), + base::BindOnce( + [](device::mojom::XRSessionPtr session, + mojom::XRSessionControllerPtr immersive_session_controller) {})); } void ExitPresent() { device_->StopSession(); - immersive_session_controller_ = nullptr; } bool presenting() { return device_->IsPresenting(); } @@ -64,10 +60,8 @@ class VRDisplayImplTest : public testing::Test { FakeVRServiceClient* client() { return client_.get(); } base::MessageLoop message_loop_; - bool is_request_presenting_success_ = false; std::unique_ptr<FakeVRDevice> device_; std::unique_ptr<FakeVRServiceClient> client_; - mojom::XRSessionControllerPtr immersive_session_controller_; DISALLOW_COPY_AND_ASSIGN(VRDisplayImplTest); }; @@ -77,6 +71,7 @@ TEST_F(VRDisplayImplTest, DevicePresentationIsolation) { std::unique_ptr<VRDisplayImpl> display_1 = MakeDisplay(&controller1); static_cast<mojom::XRSessionController*>(display_1.get()) ->SetFrameDataRestricted(false); + mojom::XRSessionControllerPtr controller2; std::unique_ptr<VRDisplayImpl> display_2 = MakeDisplay(&controller2); static_cast<mojom::XRSessionController*>(display_2.get()) @@ -92,31 +87,36 @@ TEST_F(VRDisplayImplTest, DevicePresentationIsolation) { EXPECT_EQ(expect_null, !data); }; - static_cast<mojom::VRMagicWindowProvider*>(display_1.get()) + static_cast<mojom::XRFrameDataProvider*>(display_1.get()) ->GetFrameData(base::BindOnce(callback, false, &was_called)); + base::RunLoop().RunUntilIdle(); EXPECT_TRUE(was_called); was_called = false; - static_cast<mojom::VRMagicWindowProvider*>(display_2.get()) + + static_cast<mojom::XRFrameDataProvider*>(display_2.get()) ->GetFrameData(base::BindOnce(callback, false, &was_called)); + base::RunLoop().RunUntilIdle(); EXPECT_TRUE(was_called); was_called = false; // Attempt to present. RequestSession(display_1.get()); - EXPECT_TRUE(is_request_presenting_success_); EXPECT_TRUE(presenting()); EXPECT_TRUE(device()->HasExclusiveSession()); - // While a device is presenting, noone should have access to magic window. - static_cast<mojom::VRMagicWindowProvider*>(display_1.get()) + // While a device is presenting, no one should have access to magic window. + static_cast<mojom::XRFrameDataProvider*>(display_1.get()) ->GetFrameData(base::BindOnce(callback, true, &was_called)); + base::RunLoop().RunUntilIdle(); EXPECT_TRUE(was_called); was_called = false; - static_cast<mojom::VRMagicWindowProvider*>(display_2.get()) + + static_cast<mojom::XRFrameDataProvider*>(display_2.get()) ->GetFrameData(base::BindOnce(callback, true, &was_called)); + base::RunLoop().RunUntilIdle(); EXPECT_TRUE(was_called); was_called = false; @@ -128,13 +128,16 @@ TEST_F(VRDisplayImplTest, DevicePresentationIsolation) { // Once presentation had ended both services should be able to access the // device. - static_cast<mojom::VRMagicWindowProvider*>(display_1.get()) + static_cast<mojom::XRFrameDataProvider*>(display_1.get()) ->GetFrameData(base::BindOnce(callback, false, &was_called)); + base::RunLoop().RunUntilIdle(); EXPECT_TRUE(was_called); was_called = false; - static_cast<mojom::VRMagicWindowProvider*>(display_2.get()) + + static_cast<mojom::XRFrameDataProvider*>(display_2.get()) ->GetFrameData(base::BindOnce(callback, false, &was_called)); + base::RunLoop().RunUntilIdle(); EXPECT_TRUE(was_called); was_called = false; |