diff options
Diffstat (limited to 'chromium/device')
204 files changed, 5864 insertions, 1937 deletions
diff --git a/chromium/device/BUILD.gn b/chromium/device/BUILD.gn index 47e9e690b14..fc0f4fcc084 100644 --- a/chromium/device/BUILD.gn +++ b/chromium/device/BUILD.gn @@ -71,41 +71,6 @@ test("device_unittests") { "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/bio/enrollment_handler_unittest.cc", - "fido/ble_adapter_manager_unittest.cc", - "fido/cable/fido_ble_connection_unittest.cc", - "fido/cable/fido_ble_frames_unittest.cc", - "fido/cable/fido_ble_transaction_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/cable/v2_handshake_unittest.cc", - "fido/cbor_extract_unittest.cc", - "fido/credential_management_handler_unittest.cc", - "fido/ctap_request_unittest.cc", - "fido/ctap_response_unittest.cc", - "fido/fake_fido_discovery_unittest.cc", - "fido/fido_device_discovery_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", - "fido/mac/make_credential_operation_unittest_mac.mm", - "fido/mac/util_unittest.cc", - "fido/make_credential_handler_unittest.cc", - "fido/make_credential_task_unittest.cc", - "fido/test_callback_receiver_unittest.cc", - "fido/u2f_command_constructor_unittest.cc", - "fido/u2f_register_operation_unittest.cc", - "fido/u2f_sign_operation_unittest.cc", - "fido/virtual_ctap2_device_unittest.cc", - "fido/virtual_u2f_device_unittest.cc", - "fido/win/type_conversions_unittest.cc", "gamepad/abstract_haptic_gamepad_unittest.cc", "gamepad/dualshock4_controller_unittest.cc", "gamepad/gamepad_blocklist_unittest.cc", @@ -139,7 +104,6 @@ test("device_unittests") { "//device/bluetooth:mocks", "//device/bluetooth/uribeacon", "//device/fido", - "//device/fido:mocks", "//device/fido:test_support", "//device/gamepad", "//device/gamepad:test_helpers", @@ -161,7 +125,50 @@ test("device_unittests") { "//ui/resources:ui_test_pak_data", ] - # U2F: + # Most of the FIDO implementation is not compiled in for Android. + if (!is_android) { + sources += [ + "fido/attestation_statement_formats_unittest.cc", + "fido/bio/enrollment_handler_unittest.cc", + "fido/ble_adapter_manager_unittest.cc", + "fido/cable/fido_ble_connection_unittest.cc", + "fido/cable/fido_ble_frames_unittest.cc", + "fido/cable/fido_ble_transaction_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/cable/v2_handshake_unittest.cc", + "fido/cbor_extract_unittest.cc", + "fido/credential_management_handler_unittest.cc", + "fido/ctap_request_unittest.cc", + "fido/ctap_response_unittest.cc", + "fido/fake_fido_discovery_unittest.cc", + "fido/fido_device_discovery_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", + "fido/mac/make_credential_operation_unittest_mac.mm", + "fido/mac/util_unittest.cc", + "fido/make_credential_handler_unittest.cc", + "fido/make_credential_task_unittest.cc", + "fido/test_callback_receiver_unittest.cc", + "fido/u2f_command_constructor_unittest.cc", + "fido/u2f_register_operation_unittest.cc", + "fido/u2f_sign_operation_unittest.cc", + "fido/virtual_ctap2_device_unittest.cc", + "fido/virtual_u2f_device_unittest.cc", + "fido/win/type_conversions_unittest.cc", + ] + + deps += [ "//device/fido:mocks" ] + } + + # FIDO HID: # Android doesn't compile. # Linux, requires udev. if (!is_linux_without_udev && !is_android) { @@ -307,6 +314,8 @@ test("device_unittests") { "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_session_winrt.cc", + "bluetooth/test/fake_gatt_session_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", diff --git a/chromium/device/base/device_monitor_win.cc b/chromium/device/base/device_monitor_win.cc index faf45748f60..613ef26aad1 100644 --- a/chromium/device/base/device_monitor_win.cc +++ b/chromium/device/base/device_monitor_win.cc @@ -15,6 +15,7 @@ #include "base/at_exit.h" #include "base/bind.h" #include "base/bind_helpers.h" +#include "base/logging.h" #include "base/macros.h" #include "base/strings/string_util.h" #include "base/strings/sys_string_conversions.h" diff --git a/chromium/device/base/features.cc b/chromium/device/base/features.cc index e3dae0de35d..e2bb9c2f442 100644 --- a/chromium/device/base/features.cc +++ b/chromium/device/base/features.cc @@ -13,6 +13,13 @@ const base::Feature kNewUsbBackend{"NewUsbBackend", base::FEATURE_DISABLED_BY_DEFAULT}; const base::Feature kNewBLEWinImplementation{"NewBLEWinImplementation", base::FEATURE_ENABLED_BY_DEFAULT}; + +// Controls whether a more reliable GATT session handling +// implementation is used on Windows 10 1709 (RS3) and beyond. +// +// Disabled due to crbug/1120338. +const base::Feature kNewBLEGattSessionHandling{ + "NewBLEGattSessionHandling", base::FEATURE_DISABLED_BY_DEFAULT}; #endif // defined(OS_WIN) #if BUILDFLAG(ENABLE_VR) diff --git a/chromium/device/base/features.h b/chromium/device/base/features.h index be3d3c9135f..7c24b68685d 100644 --- a/chromium/device/base/features.h +++ b/chromium/device/base/features.h @@ -15,6 +15,7 @@ namespace device { #if defined(OS_WIN) DEVICE_BASE_EXPORT extern const base::Feature kNewUsbBackend; DEVICE_BASE_EXPORT extern const base::Feature kNewBLEWinImplementation; +DEVICE_BASE_EXPORT extern const base::Feature kNewBLEGattSessionHandling; #endif // defined(OS_WIN) #if BUILDFLAG(ENABLE_VR) diff --git a/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothRemoteGattCharacteristic.java b/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothRemoteGattCharacteristic.java index 41344a1b708..39156474261 100644 --- a/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothRemoteGattCharacteristic.java +++ b/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothRemoteGattCharacteristic.java @@ -130,11 +130,14 @@ final class ChromeBluetoothRemoteGattCharacteristic { // Implements BluetoothRemoteGattCharacteristicAndroid::WriteRemoteCharacteristic. @CalledByNative - private boolean writeRemoteCharacteristic(byte[] value) { + private boolean writeRemoteCharacteristic(byte[] value, int writeType) { if (!mCharacteristic.setValue(value)) { Log.i(TAG, "writeRemoteCharacteristic setValue failed."); return false; } + if (writeType != 0) { + mCharacteristic.setWriteType(writeType); + } if (!mChromeDevice.mBluetoothGatt.writeCharacteristic(mCharacteristic)) { Log.i(TAG, "writeRemoteCharacteristic writeCharacteristic failed."); return false; diff --git a/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/Wrappers.java b/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/Wrappers.java index 14984469ca1..3829a8ff345 100644 --- a/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/Wrappers.java +++ b/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/Wrappers.java @@ -605,6 +605,10 @@ class Wrappers { public boolean setValue(byte[] value) { return mCharacteristic.setValue(value); } + + public void setWriteType(int writeType) { + mCharacteristic.setWriteType(writeType); + } } /** diff --git a/chromium/device/bluetooth/bluetooth_adapter.cc b/chromium/device/bluetooth/bluetooth_adapter.cc index 02207f53fc7..34d5ac03ac5 100644 --- a/chromium/device/bluetooth/bluetooth_adapter.cc +++ b/chromium/device/bluetooth/bluetooth_adapter.cc @@ -10,6 +10,7 @@ #include "base/bind.h" #include "base/bind_helpers.h" +#include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/metrics/histogram_macros.h" #include "base/single_thread_task_runner.h" diff --git a/chromium/device/bluetooth/bluetooth_adapter_mac.mm b/chromium/device/bluetooth/bluetooth_adapter_mac.mm index 0b2c4e6d169..b29b68be6fa 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_mac.mm +++ b/chromium/device/bluetooth/bluetooth_adapter_mac.mm @@ -15,6 +15,7 @@ #include "base/bind.h" #include "base/compiler_specific.h" #include "base/location.h" +#include "base/logging.h" #include "base/mac/mac_util.h" #include "base/memory/ptr_util.h" #include "base/numerics/safe_conversions.h" diff --git a/chromium/device/bluetooth/bluetooth_adapter_mac_metrics.mm b/chromium/device/bluetooth/bluetooth_adapter_mac_metrics.mm index a9cd7606177..36eb4a0c23e 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_mac_metrics.mm +++ b/chromium/device/bluetooth/bluetooth_adapter_mac_metrics.mm @@ -9,6 +9,7 @@ #include "base/mac/mac_util.h" #include "base/metrics/histogram_functions.h" +#include "base/notreached.h" namespace { diff --git a/chromium/device/bluetooth/bluetooth_adapter_mac_metrics_unittest.mm b/chromium/device/bluetooth/bluetooth_adapter_mac_metrics_unittest.mm index 00d17c175be..974e3128fd0 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_mac_metrics_unittest.mm +++ b/chromium/device/bluetooth/bluetooth_adapter_mac_metrics_unittest.mm @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/logging.h" #include "base/test/metrics/histogram_tester.h" #include "device/bluetooth/test/bluetooth_test_mac.h" diff --git a/chromium/device/bluetooth/bluetooth_adapter_unittest.cc b/chromium/device/bluetooth/bluetooth_adapter_unittest.cc index 55f6b2d8333..593c5782a30 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_unittest.cc +++ b/chromium/device/bluetooth/bluetooth_adapter_unittest.cc @@ -14,6 +14,7 @@ #include "base/barrier_closure.h" #include "base/bind.h" #include "base/bind_helpers.h" +#include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/run_loop.h" #include "base/test/bind_test_util.h" @@ -131,12 +132,6 @@ class TestBluetoothAdapter final : public BluetoothAdapter { void TestErrorCallback() {} - void TestOnStartDiscoverySession( - std::unique_ptr<device::BluetoothDiscoverySession> discovery_session) { - ++callback_count_; - discovery_sessions_holder_.push(std::move(discovery_session)); - } - void OnStartDiscoverySessionQuitLoop( base::Closure run_loop_quit, std::unique_ptr<device::BluetoothDiscoverySession> discovery_session) { @@ -723,7 +718,7 @@ TEST_F(BluetoothTest, MAYBE_ConstructDefaultAdapter) { EXPECT_EQ(adapter_->IsPowered(), adapter_->IsPowered()); EXPECT_FALSE(adapter_->IsDiscoverable()); EXPECT_FALSE(adapter_->IsDiscovering()); -} +} // namespace device // TODO(scheib): Enable BluetoothTest fixture tests on all platforms. #if defined(OS_ANDROID) || defined(OS_MACOSX) @@ -1420,6 +1415,34 @@ TEST_F(BluetoothTest, MAYBE_TogglePowerBeforeScan) { EXPECT_TRUE(discovery_sessions_[0]->IsActive()); } +#if defined(OS_WIN) +TEST_P(BluetoothTestWinrtOnly, DiscoverySessionFailure) { + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + + InitWithFakeAdapter(); + TestBluetoothAdapterObserver observer(adapter_); + EXPECT_FALSE(adapter_->IsDiscovering()); + + StartLowEnergyDiscoverySession(); + EXPECT_EQ(1, callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_TRUE(adapter_->IsDiscovering()); + EXPECT_EQ(1, observer.discovering_changed_count()); + EXPECT_TRUE(observer.last_discovering()); + ASSERT_EQ((size_t)1, discovery_sessions_.size()); + EXPECT_TRUE(discovery_sessions_[0]->IsActive()); + + SimulateLowEnergyDiscoveryFailure(); + EXPECT_FALSE(adapter_->IsDiscovering()); + EXPECT_FALSE(discovery_sessions_[0]->IsActive()); + EXPECT_EQ(2, observer.discovering_changed_count()); + EXPECT_FALSE(observer.last_discovering()); +} +#endif // defined(OS_WIN) + #if defined(OS_MACOSX) #define MAYBE_TurnOffAdapterWithConnectedDevice \ TurnOffAdapterWithConnectedDevice @@ -2093,9 +2116,15 @@ TEST_F(BluetoothTest, DiscoverConnectedLowEnergyDeviceTwice) { #endif // defined(OS_MACOSX) #if defined(OS_WIN) -INSTANTIATE_TEST_SUITE_P(All, BluetoothTestWinrt, ::testing::Bool()); +INSTANTIATE_TEST_SUITE_P(All, + BluetoothTestWinrt, + ::testing::ValuesIn(kBluetoothTestWinrtParamAll)); + +INSTANTIATE_TEST_SUITE_P( + All, + BluetoothTestWinrtOnly, + ::testing::ValuesIn(kBluetoothTestWinrtParamWinrtOnly)); -INSTANTIATE_TEST_SUITE_P(All, BluetoothTestWinrtOnly, ::testing::Values(true)); #endif // defined(OS_WIN) } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_adapter_winrt.cc b/chromium/device/bluetooth/bluetooth_adapter_winrt.cc index 0fd2637cd33..1b380be9c80 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_winrt.cc +++ b/chromium/device/bluetooth/bluetooth_adapter_winrt.cc @@ -51,6 +51,7 @@ namespace { namespace uwp { using ABI::Windows::Devices::Bluetooth::BluetoothAdapter; } // namespace uwp +using ABI::Windows::Devices::Bluetooth::BluetoothError; using ABI::Windows::Devices::Bluetooth::IBluetoothAdapter; using ABI::Windows::Devices::Bluetooth::IBluetoothAdapterStatics; using ABI::Windows::Devices::Bluetooth::IID_IBluetoothAdapterStatics; @@ -64,7 +65,6 @@ 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:: @@ -72,8 +72,6 @@ using ABI::Windows::Devices::Bluetooth::Advertisement:: 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; @@ -463,26 +461,6 @@ base::Optional<std::string> ExtractDeviceName( return base::win::ScopedHString(local_name).GetAsUTF8(); } -void ExtractAndUpdateAdvertisementData( - IBluetoothLEAdvertisementReceivedEventArgs* received, - BluetoothDevice* device) { - int16_t rssi = 0; - HRESULT hr = received->get_RawSignalStrengthInDBm(&rssi); - if (FAILED(hr)) { - BLUETOOTH_LOG(ERROR) << "get_RawSignalStrengthInDBm() failed: " - << logging::SystemErrorCodeToString(hr); - } - - ComPtr<IBluetoothLEAdvertisement> advertisement = GetAdvertisement(received); - static_cast<BluetoothDeviceWinrt*>(device)->UpdateLocalName( - ExtractDeviceName(advertisement.Get())); - device->UpdateAdvertisementData(rssi, ExtractFlags(advertisement.Get()), - ExtractAdvertisedUUIDs(advertisement.Get()), - ExtractTxPower(advertisement.Get()), - ExtractServiceData(advertisement.Get()), - ExtractManufacturerData(advertisement.Get())); -} - RadioState GetState(IRadio* radio) { RadioState state; HRESULT hr = radio->get_State(&state); @@ -951,12 +929,12 @@ void BluetoothAdapterWinrt::StartScanWithFilter( return; } - auto advertisement_received_token = AddTypedEventHandler( + advertisement_received_token_ = AddTypedEventHandler( ble_advertisement_watcher_.Get(), &IBluetoothLEAdvertisementWatcher::add_Received, base::BindRepeating(&BluetoothAdapterWinrt::OnAdvertisementReceived, weak_ptr_factory_.GetWeakPtr())); - if (!advertisement_received_token) { + if (!advertisement_received_token_) { ui_task_runner_->PostTask( FROM_HERE, base::BindOnce(std::move(callback), /*is_error=*/true, @@ -964,13 +942,25 @@ void BluetoothAdapterWinrt::StartScanWithFilter( return; } - advertisement_received_token_ = *advertisement_received_token; + advertisement_watcher_stopped_token_ = AddTypedEventHandler( + ble_advertisement_watcher_.Get(), + &IBluetoothLEAdvertisementWatcher::add_Stopped, + base::BindRepeating(&BluetoothAdapterWinrt::OnAdvertisementWatcherStopped, + weak_ptr_factory_.GetWeakPtr())); + if (!advertisement_watcher_stopped_token_) { + RemoveAdvertisementWatcherEventHandlers(); + ui_task_runner_->PostTask( + FROM_HERE, + base::BindOnce(std::move(callback), /*is_error=*/true, + UMABluetoothDiscoverySessionOutcome::UNKNOWN)); + return; + } hr = ble_advertisement_watcher_->Start(); if (FAILED(hr)) { BLUETOOTH_LOG(ERROR) << "Starting the Advertisement Watcher failed: " << logging::SystemErrorCodeToString(hr); - RemoveAdvertisementReceivedHandler(); + RemoveAdvertisementWatcherEventHandlers(); ui_task_runner_->PostTask( FROM_HERE, base::BindOnce(std::move(callback), /*is_error=*/true, @@ -987,7 +977,7 @@ void BluetoothAdapterWinrt::StartScanWithFilter( BLUETOOTH_LOG(ERROR) << "Starting Advertisement Watcher failed, it is in the Aborted " "state."; - RemoveAdvertisementReceivedHandler(); + RemoveAdvertisementWatcherEventHandlers(); ui_task_runner_->PostTask( FROM_HERE, base::BindOnce(std::move(callback), /*is_error=*/true, @@ -995,6 +985,10 @@ void BluetoothAdapterWinrt::StartScanWithFilter( return; } + for (auto& observer : observers_) { + observer.AdapterDiscoveringChanged(this, /*discovering=*/true); + } + ui_task_runner_->PostTask( FROM_HERE, base::BindOnce(std::move(callback), false, UMABluetoothDiscoverySessionOutcome::SUCCESS)); @@ -1003,7 +997,7 @@ void BluetoothAdapterWinrt::StartScanWithFilter( void BluetoothAdapterWinrt::StopScan(DiscoverySessionResultCallback callback) { DCHECK_EQ(NumDiscoverySessions(), 0); - RemoveAdvertisementReceivedHandler(); + RemoveAdvertisementWatcherEventHandlers(); HRESULT hr = ble_advertisement_watcher_->Stop(); if (FAILED(hr)) { BLUETOOTH_LOG(ERROR) << "Stopped the Advertisement Watcher failed: " @@ -1015,8 +1009,14 @@ void BluetoothAdapterWinrt::StopScan(DiscoverySessionResultCallback callback) { return; } - for (auto& device : devices_) + for (auto& device : devices_) { device.second->ClearAdvertisementData(); + } + + for (auto& observer : observers_) { + observer.AdapterDiscoveringChanged(this, /*discovering=*/false); + } + ble_advertisement_watcher_.Reset(); ui_task_runner_->PostTask( FROM_HERE, base::BindOnce(std::move(callback), /*is_error=*/false, @@ -1329,14 +1329,58 @@ void BluetoothAdapterWinrt::OnAdvertisementReceived( } BluetoothDevice* const device = it->second.get(); - ExtractAndUpdateAdvertisementData(received, device); + + int16_t rssi = 0; + hr = received->get_RawSignalStrengthInDBm(&rssi); + if (FAILED(hr)) { + BLUETOOTH_LOG(ERROR) << "get_RawSignalStrengthInDBm() failed: " + << logging::SystemErrorCodeToString(hr); + } + + // Extract the remaining advertisement data. + ComPtr<IBluetoothLEAdvertisement> advertisement = GetAdvertisement(received); + base::Optional<std::string> device_name = + ExtractDeviceName(advertisement.Get()); + base::Optional<int8_t> tx_power = ExtractTxPower(advertisement.Get()); + BluetoothDevice::UUIDList advertised_uuids = + ExtractAdvertisedUUIDs(advertisement.Get()); + BluetoothDevice::ServiceDataMap service_data_map = + ExtractServiceData(advertisement.Get()); + BluetoothDevice::ManufacturerDataMap manufacturer_data_map = + ExtractManufacturerData(advertisement.Get()); + + static_cast<BluetoothDeviceWinrt*>(device)->UpdateLocalName(device_name); + device->UpdateAdvertisementData(rssi, ExtractFlags(advertisement.Get()), + advertised_uuids, tx_power, service_data_map, + manufacturer_data_map); for (auto& observer : observers_) { + observer.DeviceAdvertisementReceived( + bluetooth_address, device->GetName(), + /*advertisement_name=*/device_name, rssi, tx_power, + device->GetAppearance(), advertised_uuids, service_data_map, + manufacturer_data_map); is_new_device ? observer.DeviceAdded(this, device) : observer.DeviceChanged(this, device); } } +void BluetoothAdapterWinrt::OnAdvertisementWatcherStopped( + ABI::Windows::Devices::Bluetooth::Advertisement:: + IBluetoothLEAdvertisementWatcher* watcher, + ABI::Windows::Devices::Bluetooth::Advertisement:: + IBluetoothLEAdvertisementWatcherStoppedEventArgs* args) { + BluetoothError error; + HRESULT hr = args->get_Error(&error); + if (FAILED(hr)) { + BLUETOOTH_LOG(ERROR) << "get_Error() failed: " << hr; + return; + } + BLUETOOTH_LOG(DEBUG) << "OnAdvertisementWatcherStopped() error=" << error; + + MarkDiscoverySessionsAsInactive(); +} + void BluetoothAdapterWinrt::OnRegisterAdvertisement( BluetoothAdvertisement* advertisement, const CreateAdvertisementCallback& callback) { @@ -1409,13 +1453,23 @@ void BluetoothAdapterWinrt::TryRemovePoweredRadioEventHandlers() { } } -void BluetoothAdapterWinrt::RemoveAdvertisementReceivedHandler() { +void BluetoothAdapterWinrt::RemoveAdvertisementWatcherEventHandlers() { DCHECK(ble_advertisement_watcher_); - HRESULT hr = ble_advertisement_watcher_->remove_Received( - advertisement_received_token_); - if (FAILED(hr)) { - BLUETOOTH_LOG(ERROR) << "Removing the Received Handler failed: " - << logging::SystemErrorCodeToString(hr); + if (advertisement_received_token_) { + HRESULT hr = ble_advertisement_watcher_->remove_Received( + *advertisement_received_token_); + if (FAILED(hr)) { + BLUETOOTH_LOG(ERROR) << "Removing the Received Handler failed: " + << logging::SystemErrorCodeToString(hr); + } + } + if (advertisement_watcher_stopped_token_) { + HRESULT hr = ble_advertisement_watcher_->remove_Stopped( + *advertisement_watcher_stopped_token_); + if (FAILED(hr)) { + BLUETOOTH_LOG(ERROR) << "Removing the Stopped Handler failed: " + << logging::SystemErrorCodeToString(hr); + } } } diff --git a/chromium/device/bluetooth/bluetooth_adapter_winrt.h b/chromium/device/bluetooth/bluetooth_adapter_winrt.h index 9bb463abbf9..fbb47308d9d 100644 --- a/chromium/device/bluetooth/bluetooth_adapter_winrt.h +++ b/chromium/device/bluetooth/bluetooth_adapter_winrt.h @@ -198,7 +198,13 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterWinrt : public BluetoothAdapter { ABI::Windows::Devices::Bluetooth::Advertisement:: IBluetoothLEAdvertisementWatcher* watcher, ABI::Windows::Devices::Bluetooth::Advertisement:: - IBluetoothLEAdvertisementReceivedEventArgs* received); + IBluetoothLEAdvertisementReceivedEventArgs* args); + + void OnAdvertisementWatcherStopped( + ABI::Windows::Devices::Bluetooth::Advertisement:: + IBluetoothLEAdvertisementWatcher* watcher, + ABI::Windows::Devices::Bluetooth::Advertisement:: + IBluetoothLEAdvertisementWatcherStoppedEventArgs* args); void OnRegisterAdvertisement(BluetoothAdvertisement* advertisement, const CreateAdvertisementCallback& callback); @@ -212,7 +218,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterWinrt : public BluetoothAdapter { void TryRemovePoweredRadioEventHandlers(); - void RemoveAdvertisementReceivedHandler(); + void RemoveAdvertisementWatcherEventHandlers(); bool is_initialized_ = false; bool radio_access_allowed_ = false; @@ -237,7 +243,8 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterWinrt : public BluetoothAdapter { std::vector<scoped_refptr<BluetoothAdvertisement>> pending_advertisements_; - EventRegistrationToken advertisement_received_token_; + base::Optional<EventRegistrationToken> advertisement_received_token_; + base::Optional<EventRegistrationToken> advertisement_watcher_stopped_token_; Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::Advertisement:: IBluetoothLEAdvertisementWatcher> ble_advertisement_watcher_; diff --git a/chromium/device/bluetooth/bluetooth_device.cc b/chromium/device/bluetooth/bluetooth_device.cc index a567ff6d9db..c5cbedcc978 100644 --- a/chromium/device/bluetooth/bluetooth_device.cc +++ b/chromium/device/bluetooth/bluetooth_device.cc @@ -10,6 +10,7 @@ #include <string> #include <utility> +#include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" diff --git a/chromium/device/bluetooth/bluetooth_device_unittest.cc b/chromium/device/bluetooth/bluetooth_device_unittest.cc index e2f11788406..d46bfd80480 100644 --- a/chromium/device/bluetooth/bluetooth_device_unittest.cc +++ b/chromium/device/bluetooth/bluetooth_device_unittest.cc @@ -7,6 +7,7 @@ #include <stddef.h> #include "base/bind.h" +#include "base/logging.h" #include "base/run_loop.h" #include "base/stl_util.h" #include "base/strings/utf_string_conversions.h" @@ -584,10 +585,20 @@ TEST_F(BluetoothTest, MAYBE_AdvertisementData_Discovery) { EXPECT_EQ(ToInt8(TestTxPower::LOWEST), device->GetInquiryTxPower().value()); } -#if defined(OS_ANDROID) || defined(OS_MACOSX) // TODO(dougt) As I turn on new platforms for WebBluetooth Scanning, // I will relax this #ifdef -TEST_F(BluetoothTest, DeviceAdvertisementReceived) { +#if defined(OS_ANDROID) || defined(OS_MACOSX) || defined(OS_WIN) +#define MAYBE_DeviceAdvertisementReceived DeviceAdvertisementReceived +#else +#define MAYBE_DeviceAdvertisementReceived DISABLED_DeviceAdvertisementReceived +#endif +// Tests that the Bluetooth adapter observer is notified when a device +// advertisement is received. +#if defined(OS_WIN) +TEST_P(BluetoothTestWinrtOnly, DeviceAdvertisementReceived) { +#else +TEST_F(BluetoothTest, MAYBE_DeviceAdvertisementReceived) { +#endif if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -599,28 +610,34 @@ TEST_F(BluetoothTest, DeviceAdvertisementReceived) { StartLowEnergyDiscoverySession(); SimulateLowEnergyDevice(1); - EXPECT_EQ(1, observer.device_advertisement_raw_received_count()); - EXPECT_EQ(kTestDeviceAddress1, - observer.device_last_device_name().value_or("")); - EXPECT_EQ(kTestDeviceName, - observer.device_last_advertisement_name().value_or("")); + ASSERT_EQ(1, observer.device_advertisement_raw_received_count()); + EXPECT_EQ(kTestDeviceName, observer.last_device_name().value_or("")); + EXPECT_EQ(kTestDeviceName, observer.last_advertisement_name().value_or("")); + EXPECT_EQ(static_cast<int>(TestRSSI::LOWEST), + observer.last_rssi().value_or(-1)); + EXPECT_EQ(static_cast<int>(TestTxPower::LOWEST), + observer.last_tx_power().value_or(-1)); - // TestRSSI::LOWEST - EXPECT_EQ(-81, observer.device_last_rssi().value_or(-1)); + // BluetoothDevice::GetAppearance() is not implemented on all platforms. + // TODO(crbug.com/588083): Check this property when it is implemented. - // TestTxPower::LOWEST - EXPECT_EQ(-40, observer.device_last_tx_power().value_or(-1)); + const device::BluetoothDevice::UUIDList kTestAdvertisedUUIDs = { + BluetoothUUID(kTestUUIDGenericAccess), + BluetoothUUID(kTestUUIDGenericAttribute)}; + EXPECT_EQ(kTestAdvertisedUUIDs, observer.last_advertised_uuids()); - // TODO(crbug.com/588083) - // EXPECT_EQ(0x04, observer.device_last_appearance()); + const device::BluetoothDevice::ServiceDataMap kTestServiceDataMap = { + {BluetoothUUID(kTestUUIDHeartRate), {1}}}; + EXPECT_EQ(kTestServiceDataMap, observer.last_service_data_map()); - // TODO(dougt): Service Data, ManufacturerData, Advertised UUID + const device::BluetoothDevice::ManufacturerDataMap kTestManufacturerDataMap = + {{kTestManufacturerId, {1, 2, 3, 4}}}; + EXPECT_EQ(kTestManufacturerDataMap, observer.last_manufacturer_data_map()); // Double check that we can receive another advertisement. SimulateLowEnergyDevice(2); EXPECT_EQ(2, observer.device_advertisement_raw_received_count()); } -#endif #if defined(OS_ANDROID) || defined(OS_MACOSX) #define MAYBE_GetUUIDs_Connection GetUUIDs_Connection diff --git a/chromium/device/bluetooth/bluetooth_device_winrt.cc b/chromium/device/bluetooth/bluetooth_device_winrt.cc index b1785fa92e6..bdb2afdc94c 100644 --- a/chromium/device/bluetooth/bluetooth_device_winrt.cc +++ b/chromium/device/bluetooth/bluetooth_device_winrt.cc @@ -32,26 +32,30 @@ namespace { using ABI::Windows::Devices::Bluetooth::BluetoothConnectionStatus; using ABI::Windows::Devices::Bluetooth::BluetoothConnectionStatus_Connected; +using ABI::Windows::Devices::Bluetooth::BluetoothError; +using ABI::Windows::Devices::Bluetooth::BluetoothError_Success; using ABI::Windows::Devices::Bluetooth::BluetoothLEDevice; +using ABI::Windows::Devices::Bluetooth::IBluetoothDeviceId; +using ABI::Windows::Devices::Bluetooth::IBluetoothDeviceIdStatics; +using ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice; +using ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice2; +using ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice4; +using ABI::Windows::Devices::Bluetooth::IBluetoothLEDeviceStatics; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::GattSession; using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: - GattCommunicationStatus; + GattSessionStatus; using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: - GattCommunicationStatus_Success; + GattSessionStatus_Active; using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: - GattDeviceServicesResult; + GattSessionStatus_Closed; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattSession; using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: - IGattDeviceServicesResult; -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; + IGattSessionStatics; using ABI::Windows::Devices::Enumeration::IDeviceInformation; +using ABI::Windows::Devices::Enumeration::IDeviceInformation2; 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::Devices::Enumeration::IDeviceInformationPairing2; using ABI::Windows::Foundation::IAsyncOperation; using ABI::Windows::Foundation::IClosable; using Microsoft::WRL::ComPtr; @@ -118,7 +122,26 @@ void CloseDevice(ComPtr<IBluetoothLEDevice> ble_device) { hr = closable->Close(); if (FAILED(hr)) { - BLUETOOTH_LOG(DEBUG) << "IClosable::close() failed: " + BLUETOOTH_LOG(DEBUG) << "IClosable::Close() failed: " + << logging::SystemErrorCodeToString(hr); + } +} + +void CloseGattSession(ComPtr<IGattSession> gatt_session) { + if (!gatt_session) + return; + + ComPtr<IClosable> closable; + HRESULT hr = gatt_session.As(&closable); + if (FAILED(hr)) { + BLUETOOTH_LOG(DEBUG) << "As IClosable failed: " + << logging::SystemErrorCodeToString(hr); + return; + } + + hr = closable->Close(); + if (FAILED(hr)) { + BLUETOOTH_LOG(DEBUG) << "IClosable::Close() failed: " << logging::SystemErrorCodeToString(hr); } } @@ -132,6 +155,15 @@ void RemoveConnectionStatusHandler(IBluetoothLEDevice* ble_device, } } +void RemoveGattSessionStatusHandler(IGattSession* gatt_session, + EventRegistrationToken token) { + HRESULT hr = gatt_session->remove_SessionStatusChanged(token); + if (FAILED(hr)) { + BLUETOOTH_LOG(DEBUG) << "Removing ConnectionStatus Handler failed: " + << logging::SystemErrorCodeToString(hr); + } +} + void RemoveGattServicesChangedHandler(IBluetoothLEDevice* ble_device, EventRegistrationToken token) { HRESULT hr = ble_device->remove_GattServicesChanged(token); @@ -161,6 +193,7 @@ BluetoothDeviceWinrt::BluetoothDeviceWinrt(BluetoothAdapterWinrt* adapter, } BluetoothDeviceWinrt::~BluetoothDeviceWinrt() { + CloseGattSession(gatt_session_); CloseDevice(ble_device_); ClearEventRegistrations(); } @@ -241,14 +274,15 @@ bool BluetoothDeviceWinrt::IsPaired() const { } bool BluetoothDeviceWinrt::IsConnected() const { - return IsGattConnected(); + return ble_device_ && + connection_status_ == BluetoothConnectionStatus_Connected; } bool BluetoothDeviceWinrt::IsGattConnected() const { - if (!ble_device_) - return false; + if (!observe_gatt_session_status_change_events_) + return IsConnected(); - return connection_status_; + return gatt_session_ && gatt_session_status_ == GattSessionStatus_Active; } bool BluetoothDeviceWinrt::IsConnectable() const { @@ -426,10 +460,7 @@ void BluetoothDeviceWinrt::CreateGattConnectionImpl( BLUETOOTH_LOG(DEBUG) << "GetBluetoothLEDeviceStaticsActivationFactory failed: " << logging::SystemErrorCodeToString(hr); - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(&BluetoothDeviceWinrt::DidFailToConnectGatt, - weak_ptr_factory_.GetWeakPtr(), - ConnectErrorCode::ERROR_FAILED)); + NotifyGattConnectFailure(); return; } @@ -444,45 +475,53 @@ void BluetoothDeviceWinrt::CreateGattConnectionImpl( BLUETOOTH_LOG(DEBUG) << "BluetoothLEDevice::FromBluetoothAddressAsync failed: " << logging::SystemErrorCodeToString(hr); - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(&BluetoothDeviceWinrt::DidFailToConnectGatt, - weak_ptr_factory_.GetWeakPtr(), - ConnectErrorCode::ERROR_FAILED)); + NotifyGattConnectFailure(); return; } - target_uuid_ = std::move(service_uuid); hr = base::win::PostAsyncResults( std::move(from_bluetooth_address_op), - base::BindOnce(&BluetoothDeviceWinrt::OnFromBluetoothAddress, - weak_ptr_factory_.GetWeakPtr())); + base::BindOnce( + &BluetoothDeviceWinrt::OnBluetoothLEDeviceFromBluetoothAddress, + weak_ptr_factory_.GetWeakPtr())); if (FAILED(hr)) { BLUETOOTH_LOG(DEBUG) << "PostAsyncResults failed: " << logging::SystemErrorCodeToString(hr); - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(&BluetoothDeviceWinrt::DidFailToConnectGatt, - weak_ptr_factory_.GetWeakPtr(), - ConnectErrorCode::ERROR_FAILED)); + NotifyGattConnectFailure(); return; } - pending_on_from_bluetooth_address_ = true; + target_uuid_ = std::move(service_uuid); + pending_gatt_service_discovery_start_ = true; +} + +void BluetoothDeviceWinrt::NotifyGattConnectFailure() { + // Reset |pending_gatt_service_discovery_start_| so that + // UpgradeToFullDiscovery() doesn't mistakenly believe GATT discovery is + // imminent and therefore avoids starting one itself. + pending_gatt_service_discovery_start_ = false; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(&BluetoothDeviceWinrt::DidFailToConnectGatt, + weak_ptr_factory_.GetWeakPtr(), + ConnectErrorCode::ERROR_FAILED)); } void BluetoothDeviceWinrt::UpgradeToFullDiscovery() { // |CreateGattConnectionImpl| has been called previously but having a specific // |target_uuid_| was too optimistic and now a complete enumeration of // services is needed. - DCHECK(pending_on_from_bluetooth_address_ || connection_status_); target_uuid_.reset(); - if (pending_on_from_bluetooth_address_) { - // |OnFromBluetoothAddress| hasn't been called yet. Updating |target_uuid_| - // will be sufficient to change the discovery that will be started. + if (pending_gatt_service_discovery_start_) { + // There is an imminent call to StartDiscovery(). Resetting |target_uuid_| + // now will be sufficient to change the discovery that will be started. return; } + DCHECK(ble_device_); + DCHECK(!observe_gatt_session_status_change_events_ || IsGattConnected()); + // Restart discovery. StartGattDiscovery(); } @@ -494,6 +533,7 @@ void BluetoothDeviceWinrt::DisconnectGatt() { // remaining references, so that the OS disconnects. // Reference: // - https://docs.microsoft.com/en-us/windows/uwp/devices-sensors/gatt-client + CloseGattSession(gatt_session_); CloseDevice(ble_device_); ClearGattServices(); @@ -511,13 +551,20 @@ HRESULT BluetoothDeviceWinrt::GetBluetoothLEDeviceStaticsActivationFactory( RuntimeClass_Windows_Devices_Bluetooth_BluetoothLEDevice>(statics); } -void BluetoothDeviceWinrt::OnFromBluetoothAddress( +HRESULT BluetoothDeviceWinrt::GetGattSessionStaticsActivationFactory( + IGattSessionStatics** statics) const { + return base::win::GetActivationFactory< + IGattSessionStatics, + RuntimeClass_Windows_Devices_Bluetooth_GenericAttributeProfile_GattSession>( + statics); +} + +void BluetoothDeviceWinrt::OnBluetoothLEDeviceFromBluetoothAddress( ComPtr<IBluetoothLEDevice> ble_device) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); - pending_on_from_bluetooth_address_ = false; if (!ble_device) { BLUETOOTH_LOG(DEBUG) << "Getting Device From Bluetooth Address failed."; - DidFailToConnectGatt(ConnectErrorCode::ERROR_FAILED); + NotifyGattConnectFailure(); return; } @@ -532,40 +579,168 @@ 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(); - - gatt_services_changed_token_ = AddTypedEventHandler( - ble_device_.Get(), &IBluetoothLEDevice::add_GattServicesChanged, - base::BindRepeating(&BluetoothDeviceWinrt::OnGattServicesChanged, - weak_ptr_factory_.GetWeakPtr())); - - // Initiating the GATT discovery will result in a GATT connection attempt as - // well and triggers OnConnectionStatusChanged on success. - StartGattDiscovery(); - name_changed_token_ = AddTypedEventHandler( ble_device_.Get(), &IBluetoothLEDevice::add_NameChanged, base::BindRepeating(&BluetoothDeviceWinrt::OnNameChanged, weak_ptr_factory_.GetWeakPtr())); + + if (!observe_gatt_session_status_change_events_) { + // GattSession SessionStatusChanged events can not be observed on + // 1703 (RS2) because BluetoothLEDevice::GetDeviceId() is not + // available. Instead, initiate GATT discovery which should result + // in a GATT connection attempt as well and trigger + // OnConnectionStatusChanged on success. + if (IsGattConnected()) { + DidConnectGatt(); + } + StartGattDiscovery(); + return; + } + + // Next, obtain a GattSession so we can tell the OS to maintain a GATT + // connection with |ble_device_|. + + // BluetoothLEDevice::GetDeviceId() + ComPtr<IBluetoothLEDevice4> ble_device_4; + HRESULT hr = ble_device_.As(&ble_device_4); + if (FAILED(hr)) { + BLUETOOTH_LOG(DEBUG) << "Obtaining IBluetoothLEDevice4 failed: " + << logging::SystemErrorCodeToString(hr); + NotifyGattConnectFailure(); + return; + } + ComPtr<IBluetoothDeviceId> bluetooth_device_id; + hr = ble_device_4->get_BluetoothDeviceId(&bluetooth_device_id); + if (FAILED(hr)) { + BLUETOOTH_LOG(DEBUG) << "BluetoothDeviceId::FromId failed: " + << logging::SystemErrorCodeToString(hr); + NotifyGattConnectFailure(); + return; + } + + // GattSession::FromDeviceIdAsync() + IGattSessionStatics* gatt_session_statics = nullptr; + hr = GetGattSessionStaticsActivationFactory(&gatt_session_statics); + if (FAILED(hr)) { + BLUETOOTH_LOG(DEBUG) << "GetGattSessionStaticsActivationFactory() failed: " + << logging::SystemErrorCodeToString(hr); + NotifyGattConnectFailure(); + return; + } + ComPtr<IAsyncOperation<GattSession*>> gatt_session_from_device_id_async_op; + hr = gatt_session_statics->FromDeviceIdAsync( + bluetooth_device_id.Get(), &gatt_session_from_device_id_async_op); + if (FAILED(hr)) { + BLUETOOTH_LOG(DEBUG) << "GattSession::FromDeviceId failed: " + << logging::SystemErrorCodeToString(hr); + NotifyGattConnectFailure(); + return; + } + hr = base::win::PostAsyncResults( + std::move(gatt_session_from_device_id_async_op), + base::BindOnce(&BluetoothDeviceWinrt::OnGattSessionFromDeviceId, + weak_ptr_factory_.GetWeakPtr())); + if (FAILED(hr)) { + BLUETOOTH_LOG(DEBUG) << "PostAsyncResults failed: " + << logging::SystemErrorCodeToString(hr); + NotifyGattConnectFailure(); + return; + } +} + +void BluetoothDeviceWinrt::OnGattSessionFromDeviceId( + ComPtr<IGattSession> gatt_session) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(observe_gatt_session_status_change_events_); + + if (!gatt_session) { + BLUETOOTH_LOG(DEBUG) << "Getting GattSession failed"; + NotifyGattConnectFailure(); + return; + } + + gatt_session_ = std::move(gatt_session); + + // Tell the OS to automatically establish and maintain a GATT connection. + HRESULT hr = gatt_session_->put_MaintainConnection(true); + if (FAILED(hr)) { + BLUETOOTH_LOG(DEBUG) << "Setting GattSession.MaintainConnection failed: " + << logging::SystemErrorCodeToString(hr); + NotifyGattConnectFailure(); + return; + } + + // Observe GattSessionStatus changes. + gatt_session_->get_SessionStatus(&gatt_session_status_); + gatt_session_status_changed_token_ = AddTypedEventHandler( + gatt_session_.Get(), &IGattSession::add_SessionStatusChanged, + base::BindRepeating(&BluetoothDeviceWinrt::OnGattSessionStatusChanged, + weak_ptr_factory_.GetWeakPtr())); + + // Check whether we missed the initial GattSessionStatus change notification + // because the OS had already established a connection. + if (IsGattConnected()) { + DidConnectGatt(); + StartGattDiscovery(); + } +} + +void BluetoothDeviceWinrt::OnGattSessionStatusChanged( + IGattSession* gatt_session, + ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattSessionStatusChangedEventArgs* event_args) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(observe_gatt_session_status_change_events_); + DCHECK_EQ(gatt_session_.Get(), gatt_session); + + GattSessionStatus old_status = gatt_session_status_; + event_args->get_Status(&gatt_session_status_); + + BluetoothError error; + event_args->get_Error(&error); + BLUETOOTH_LOG(DEBUG) << "OnGattSessionStatusChanged() status=" + << gatt_session_status_ << ", error=" << error; + + if (pending_gatt_service_discovery_start_ && + error != BluetoothError_Success) { + NotifyGattConnectFailure(); + return; + } + + // Spurious status change notifications may occur. + if (old_status == gatt_session_status_) { + return; + } + + if (IsGattConnected()) { + DidConnectGatt(); + StartGattDiscovery(); + } else { + gatt_discoverer_.reset(); + ClearGattServices(); + DidDisconnectGatt(); + } } void BluetoothDeviceWinrt::OnConnectionStatusChanged( IBluetoothLEDevice* ble_device, IInspectable* object) { - BluetoothConnectionStatus new_status; - ble_device->get_ConnectionStatus(&new_status); - // Windows sometimes returns a status changed event with a status that has - // not changed. - if (new_status == connection_status_) + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + BluetoothConnectionStatus old_status = connection_status_; + ble_device->get_ConnectionStatus(&connection_status_); + BLUETOOTH_LOG(DEBUG) << "OnConnectionStatusChanged() status=" + << connection_status_; + + // Spurious status change notifications may occur. + if (old_status == connection_status_) { return; + } + + if (observe_gatt_session_status_change_events_) { + return; + } - connection_status_ = new_status; - DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); if (IsGattConnected()) { DidConnectGatt(); } else { @@ -577,15 +752,23 @@ void BluetoothDeviceWinrt::OnConnectionStatusChanged( void BluetoothDeviceWinrt::OnGattServicesChanged(IBluetoothLEDevice* ble_device, IInspectable* object) { + BLUETOOTH_LOG(DEBUG) << "OnGattServicesChanged()"; DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); - // Note: We don't clear out |gatt_services_| here, as we don't want to break + // TODO(crbug/1085596): This event fires once for every newly discovered GATT + // service. Hence, the initial GATT service discovery aborts and restarts + // itself here once for every service discovered, which is unnecessary and + // slow. + + // 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); if (IsGattConnected()) { // In order to stop a potential ongoing GATT discovery, the GattDiscoverer // is reset and a new discovery is initiated. + BLUETOOTH_LOG(DEBUG) << "Discovering GATT services anew"; StartGattDiscovery(); } } @@ -597,6 +780,14 @@ void BluetoothDeviceWinrt::OnNameChanged(IBluetoothLEDevice* ble_device, } void BluetoothDeviceWinrt::StartGattDiscovery() { + BLUETOOTH_LOG(DEBUG) << "StartGattDiscovery()"; + pending_gatt_service_discovery_start_ = false; + if (!gatt_services_changed_token_) { + gatt_services_changed_token_ = AddTypedEventHandler( + ble_device_.Get(), &IBluetoothLEDevice::add_GattServicesChanged, + base::BindRepeating(&BluetoothDeviceWinrt::OnGattServicesChanged, + weak_ptr_factory_.GetWeakPtr())); + } gatt_discoverer_ = std::make_unique<BluetoothGattDiscovererWinrt>(ble_device_, target_uuid_); gatt_discoverer_->StartGattDiscovery( @@ -605,9 +796,11 @@ void BluetoothDeviceWinrt::StartGattDiscovery() { } void BluetoothDeviceWinrt::OnGattDiscoveryComplete(bool success) { + BLUETOOTH_LOG(DEBUG) << "OnGattDiscoveryComplete() success=" << success; if (!success) { - if (!IsGattConnected()) - DidFailToConnectGatt(ConnectErrorCode::ERROR_FAILED); + if (!IsGattConnected()) { + NotifyGattConnectFailure(); + } gatt_discoverer_.reset(); return; } @@ -661,6 +854,11 @@ void BluetoothDeviceWinrt::ClearEventRegistrations() { *connection_changed_token_); } + if (gatt_session_status_changed_token_) { + RemoveGattSessionStatusHandler(gatt_session_.Get(), + *gatt_session_status_changed_token_); + } + if (gatt_services_changed_token_) { RemoveGattServicesChangedHandler(ble_device_.Get(), *gatt_services_changed_token_); diff --git a/chromium/device/bluetooth/bluetooth_device_winrt.h b/chromium/device/bluetooth/bluetooth_device_winrt.h index 94b4b7140fb..d29c2890028 100644 --- a/chromium/device/bluetooth/bluetooth_device_winrt.h +++ b/chromium/device/bluetooth/bluetooth_device_winrt.h @@ -5,6 +5,7 @@ #ifndef DEVICE_BLUETOOTH_BLUETOOTH_DEVICE_WINRT_H_ #define DEVICE_BLUETOOTH_BLUETOOTH_DEVICE_WINRT_H_ +#include <windows.devices.bluetooth.genericattributeprofile.h> #include <windows.devices.bluetooth.h> #include <wrl/client.h> @@ -14,10 +15,13 @@ #include <string> #include "base/callback_forward.h" +#include "base/feature_list.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "base/optional.h" #include "base/threading/thread_checker.h" +#include "base/win/windows_version.h" +#include "device/base/features.h" #include "device/bluetooth/bluetooth_device.h" #include "device/bluetooth/bluetooth_export.h" @@ -100,19 +104,38 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDeviceWinrt : public BluetoothDevice { void UpgradeToFullDiscovery() override; void DisconnectGatt() override; - // This is declared virtual so that they can be overridden by tests. + // Declared virtual so that it can be overridden by tests. virtual HRESULT GetBluetoothLEDeviceStaticsActivationFactory( ABI::Windows::Devices::Bluetooth::IBluetoothLEDeviceStatics** statics) const; + // Declared virtual so that it can be overridden by tests. + virtual HRESULT GetGattSessionStaticsActivationFactory( + ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattSessionStatics** statics) const; + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice> ble_device_; + Microsoft::WRL::ComPtr< + ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattSession> + gatt_session_; private: - void OnFromBluetoothAddress( + void OnBluetoothLEDeviceFromBluetoothAddress( Microsoft::WRL::ComPtr< ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice> ble_device); + void OnGattSessionFromDeviceId( + Microsoft::WRL::ComPtr<ABI::Windows::Devices::Bluetooth:: + GenericAttributeProfile::IGattSession> + gatt_session); + + void OnGattSessionStatusChanged( + ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::IGattSession* + gatt_session, + ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + IGattSessionStatusChangedEventArgs* event_args); + void OnConnectionStatusChanged( ABI::Windows::Devices::Bluetooth::IBluetoothLEDevice* ble_device, IInspectable* object); @@ -127,23 +150,42 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDeviceWinrt : public BluetoothDevice { void StartGattDiscovery(); void OnGattDiscoveryComplete(bool success); + void NotifyGattConnectFailure(); void ClearGattServices(); void ClearEventRegistrations(); ABI::Windows::Devices::Bluetooth::BluetoothConnectionStatus connection_status_; + ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::GattSessionStatus + gatt_session_status_; uint64_t raw_address_; std::string address_; base::Optional<std::string> local_name_; std::unique_ptr<BluetoothPairingWinrt> pairing_; - bool pending_on_from_bluetooth_address_ = false; + // Indicates whether the device should subscribe to GattSession + // SessionStatusChanged events. Doing so requires calling + // BluetoothLEDevice::GetDeviceId() which is only available on 1709 + // (RS3) or newer. If false, GATT connection reliability may be + // degraded. + bool observe_gatt_session_status_change_events_ = + base::FeatureList::IsEnabled(kNewBLEGattSessionHandling) && + base::win::GetVersion() >= base::win::Version::WIN10_RS3; + + // Indicates whether a GATT service discovery is imminent. Discovery + // begins once GattSessionStatus for the device changes to |Active| + // if |observe_gatt_session_status_change_events_| is true, or once + // the BluetoothLEDevice has been obtained from + // FromBluetoothAddressAsync() otherwise. + bool pending_gatt_service_discovery_start_ = false; + base::Optional<BluetoothUUID> target_uuid_; std::unique_ptr<BluetoothGattDiscovererWinrt> gatt_discoverer_; base::Optional<EventRegistrationToken> connection_changed_token_; + base::Optional<EventRegistrationToken> gatt_session_status_changed_token_; base::Optional<EventRegistrationToken> gatt_services_changed_token_; base::Optional<EventRegistrationToken> name_changed_token_; diff --git a/chromium/device/bluetooth/bluetooth_discovery_manager_mac.mm b/chromium/device/bluetooth/bluetooth_discovery_manager_mac.mm index caa09ebd376..3331f05ba8e 100644 --- a/chromium/device/bluetooth/bluetooth_discovery_manager_mac.mm +++ b/chromium/device/bluetooth/bluetooth_discovery_manager_mac.mm @@ -7,6 +7,7 @@ #import <IOBluetooth/objc/IOBluetoothDevice.h> #import <IOBluetooth/objc/IOBluetoothDeviceInquiry.h> +#include "base/check_op.h" #include "base/logging.h" #include "base/mac/scoped_nsobject.h" #include "base/macros.h" diff --git a/chromium/device/bluetooth/bluetooth_discovery_session.cc b/chromium/device/bluetooth/bluetooth_discovery_session.cc index 9830d6c33a5..7f09a1eee21 100644 --- a/chromium/device/bluetooth/bluetooth_discovery_session.cc +++ b/chromium/device/bluetooth/bluetooth_discovery_session.cc @@ -8,6 +8,7 @@ #include "base/bind.h" #include "base/bind_helpers.h" +#include "base/logging.h" #include "device/bluetooth/bluetooth_adapter.h" #include "device/bluetooth/bluetooth_discovery_filter.h" diff --git a/chromium/device/bluetooth/bluetooth_discovery_session.h b/chromium/device/bluetooth/bluetooth_discovery_session.h index 2f94f3324b4..1d1ac97efbc 100644 --- a/chromium/device/bluetooth/bluetooth_discovery_session.h +++ b/chromium/device/bluetooth/bluetooth_discovery_session.h @@ -47,12 +47,10 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDiscoverySession { INACTIVE }; - // Destructor automatically terminates the discovery session. If this - // results in a call to the underlying system to stop device discovery - // (i.e. this instance represents the last active discovery session), - // the call may not always succeed. To be notified of such failures, - // users are highly encouraged to call BluetoothDiscoverySession::Stop, - // instead of relying on the destructor. + // Terminates the discovery session. If this is the last active discovery + // session, a call to the underlying system to stop device discovery is made. + // Users may call BluetoothDiscoverySession::Stop() if they need to observe + // the result of that operation, but this is usually unnecessary. virtual ~BluetoothDiscoverySession(); // Returns true if the session is active, false otherwise. If false, the @@ -63,14 +61,11 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDiscoverySession { // discovery continues. virtual bool IsActive() const; - // Requests this discovery session instance to stop. If this instance is - // active, the session will stop. On success, |callback| is called and - // on error |error_callback| is called. After a successful invocation, the - // adapter may or may not stop device discovery, depending on whether or not - // other active discovery sessions are present. Users are highly encouraged - // to call this method to end a discovery session, instead of relying on the - // destructor, so that they can be notified of the result via the callback - // arguments. + // Requests this discovery session instance to stop. If this is the last + // active discovery session, a call to the underlying system to stop device + // discovery is made, and |error_callback| will be invoked if such a call + // fails. Typically, users can ignore this and simply destroy the instance + // instead of calling Stop(). virtual void Stop(base::Closure callback = base::DoNothing(), ErrorCallback error_callback = base::DoNothing()); diff --git a/chromium/device/bluetooth/bluetooth_init_win.cc b/chromium/device/bluetooth/bluetooth_init_win.cc index ab41d47174b..c623f10e6c1 100644 --- a/chromium/device/bluetooth/bluetooth_init_win.cc +++ b/chromium/device/bluetooth/bluetooth_init_win.cc @@ -4,7 +4,7 @@ #include "device/bluetooth/bluetooth_init_win.h" -#include "base/threading/scoped_blocking_call.h" +#include "base/threading/scoped_thread_priority.h" namespace { @@ -28,8 +28,10 @@ bool HasBluetoothStack() { } has_bluetooth_stack = HBS_UNKNOWN; if (has_bluetooth_stack == HBS_UNKNOWN) { - base::ScopedBlockingCall scoped_blocking_call( - FROM_HERE, base::BlockingType::MAY_BLOCK); + // Mitigate the issues caused by loading DLLs on a background thread + // (http://crbug/973868). + SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY_REPEATEDLY(); + HRESULT hr = E_FAIL; __try { hr = __HrLoadAllImportsForDll("bthprops.cpl"); 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 a7b03c84975..e78ca6b190f 100644 --- a/chromium/device/bluetooth/bluetooth_low_energy_advertisement_manager_mac.mm +++ b/chromium/device/bluetooth/bluetooth_low_energy_advertisement_manager_mac.mm @@ -5,6 +5,7 @@ #include "device/bluetooth/bluetooth_low_energy_advertisement_manager_mac.h" #include "base/bind.h" +#include "base/logging.h" #include "base/mac/scoped_nsobject.h" #include "base/optional.h" #include "base/strings/sys_string_conversions.h" diff --git a/chromium/device/bluetooth/bluetooth_low_energy_device_mac.mm b/chromium/device/bluetooth/bluetooth_low_energy_device_mac.mm index 6f343a774b2..93a4ba716f2 100644 --- a/chromium/device/bluetooth/bluetooth_low_energy_device_mac.mm +++ b/chromium/device/bluetooth/bluetooth_low_energy_device_mac.mm @@ -7,6 +7,7 @@ #import <CoreFoundation/CoreFoundation.h> #include <stddef.h> +#include "base/logging.h" #include "base/mac/mac_util.h" #include "base/mac/scoped_cftyperef.h" #include "base/memory/ptr_util.h" diff --git a/chromium/device/bluetooth/bluetooth_low_energy_device_watcher_mac.mm b/chromium/device/bluetooth/bluetooth_low_energy_device_watcher_mac.mm index 955d36777f8..ad46a81a983 100644 --- a/chromium/device/bluetooth/bluetooth_low_energy_device_watcher_mac.mm +++ b/chromium/device/bluetooth/bluetooth_low_energy_device_watcher_mac.mm @@ -8,6 +8,7 @@ #include "base/bind.h" #include "base/files/file_util.h" +#include "base/logging.h" #include "base/strings/sys_string_conversions.h" #include "base/task/task_traits.h" #include "device/bluetooth/bluetooth_adapter_mac.h" diff --git a/chromium/device/bluetooth/bluetooth_low_energy_discovery_manager_mac.mm b/chromium/device/bluetooth/bluetooth_low_energy_discovery_manager_mac.mm index a128699d9ae..e9ca28150ff 100644 --- a/chromium/device/bluetooth/bluetooth_low_energy_discovery_manager_mac.mm +++ b/chromium/device/bluetooth/bluetooth_low_energy_discovery_manager_mac.mm @@ -6,6 +6,7 @@ #include <memory> +#include "base/logging.h" #include "base/mac/mac_util.h" #include "base/strings/sys_string_conversions.h" #include "device/bluetooth/bluetooth_adapter_mac.h" diff --git a/chromium/device/bluetooth/bluetooth_low_energy_win.cc b/chromium/device/bluetooth/bluetooth_low_energy_win.cc index c2a4ae29246..013e0574972 100644 --- a/chromium/device/bluetooth/bluetooth_low_energy_win.cc +++ b/chromium/device/bluetooth/bluetooth_low_energy_win.cc @@ -801,20 +801,15 @@ HRESULT BluetoothLowEnergyWrapper::ReadCharacteristicValue( HRESULT BluetoothLowEnergyWrapper::WriteCharacteristicValue( base::FilePath& service_path, const PBTH_LE_GATT_CHARACTERISTIC characteristic, - PBTH_LE_GATT_CHARACTERISTIC_VALUE new_value) { + PBTH_LE_GATT_CHARACTERISTIC_VALUE new_value, + ULONG flags) { base::File file(service_path, base::File::FLAG_OPEN | base::File::FLAG_READ | base::File::FLAG_WRITE); if (!file.IsValid()) return HRESULT_FROM_WIN32(ERROR_OPEN_FAILED); - ULONG flag = BLUETOOTH_GATT_FLAG_NONE; - if (!characteristic->IsWritable) { - DCHECK(characteristic->IsWritableWithoutResponse); - flag |= BLUETOOTH_GATT_FLAG_WRITE_WITHOUT_RESPONSE; - } - return BluetoothGATTSetCharacteristicValue( - file.GetPlatformFile(), characteristic, new_value, NULL, flag); + file.GetPlatformFile(), characteristic, new_value, {}, flags); } HRESULT BluetoothLowEnergyWrapper::RegisterGattEvents( diff --git a/chromium/device/bluetooth/bluetooth_low_energy_win.h b/chromium/device/bluetooth/bluetooth_low_energy_win.h index e582d4cbaa8..e248c97c8b8 100644 --- a/chromium/device/bluetooth/bluetooth_low_energy_win.h +++ b/chromium/device/bluetooth/bluetooth_low_energy_win.h @@ -188,7 +188,8 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothLowEnergyWrapper { virtual HRESULT WriteCharacteristicValue( base::FilePath& service_path, const PBTH_LE_GATT_CHARACTERISTIC characteristic, - PBTH_LE_GATT_CHARACTERISTIC_VALUE new_value); + PBTH_LE_GATT_CHARACTERISTIC_VALUE new_value, + ULONG flags); // Register GATT events of |event_type| in the service with service device // path |service_path|. |event_parameter| is the event's parameter. |callback| diff --git a/chromium/device/bluetooth/bluetooth_low_energy_win_fake.cc b/chromium/device/bluetooth/bluetooth_low_energy_win_fake.cc index dcf4db10232..b25021dd531 100644 --- a/chromium/device/bluetooth/bluetooth_low_energy_win_fake.cc +++ b/chromium/device/bluetooth/bluetooth_low_energy_win_fake.cc @@ -216,7 +216,15 @@ HRESULT BluetoothLowEnergyWrapperFake::ReadCharacteristicValue( HRESULT BluetoothLowEnergyWrapperFake::WriteCharacteristicValue( base::FilePath& service_path, const PBTH_LE_GATT_CHARACTERISTIC characteristic, - PBTH_LE_GATT_CHARACTERISTIC_VALUE new_value) { + PBTH_LE_GATT_CHARACTERISTIC_VALUE new_value, + ULONG flags) { + // Web Bluetooth implementation currently only supports no flags or write + // without response flag even if Windows supports other flags + if (flags != BLUETOOTH_GATT_FLAG_NONE && + flags != BLUETOOTH_GATT_FLAG_WRITE_WITHOUT_RESPONSE) { + return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); + } + GattCharacteristic* target_characteristic = GetSimulatedGattCharacteristic(service_path, characteristic); if (target_characteristic == nullptr) diff --git a/chromium/device/bluetooth/bluetooth_low_energy_win_fake.h b/chromium/device/bluetooth/bluetooth_low_energy_win_fake.h index 9f0e2c0e0c6..01c482dd35d 100644 --- a/chromium/device/bluetooth/bluetooth_low_energy_win_fake.h +++ b/chromium/device/bluetooth/bluetooth_low_energy_win_fake.h @@ -130,7 +130,8 @@ class BluetoothLowEnergyWrapperFake : public BluetoothLowEnergyWrapper { HRESULT WriteCharacteristicValue( base::FilePath& service_path, const PBTH_LE_GATT_CHARACTERISTIC characteristic, - PBTH_LE_GATT_CHARACTERISTIC_VALUE new_value) override; + PBTH_LE_GATT_CHARACTERISTIC_VALUE new_value, + ULONG flags) override; HRESULT RegisterGattEvents( base::FilePath& service_path, BTH_LE_GATT_EVENT_TYPE type, diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic.cc b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic.cc index 3d1252dc2d6..9cc33c1135c 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic.cc +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic.cc @@ -107,12 +107,6 @@ void BluetoothRemoteGattCharacteristic::StartNotifySession( } #endif -bool BluetoothRemoteGattCharacteristic::WriteWithoutResponse( - base::span<const uint8_t> value) { - NOTIMPLEMENTED(); - return false; -} - bool BluetoothRemoteGattCharacteristic::AddDescriptor( std::unique_ptr<BluetoothRemoteGattDescriptor> descriptor) { if (!descriptor) diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic.h b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic.h index 64b0875ded6..75996110686 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic.h +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic.h @@ -42,6 +42,12 @@ class BluetoothRemoteGattDescriptor; class DEVICE_BLUETOOTH_EXPORT BluetoothRemoteGattCharacteristic : public virtual BluetoothGattCharacteristic { public: + // Parameter for WriteRemoteCharacteristic + enum class WriteType { + kWithResponse, + kWithoutResponse, + }; + // The ValueCallback is used to return the value of a remote characteristic // upon a read request. using ValueCallback = base::OnceCallback<void(const std::vector<uint8_t>&)>; @@ -136,13 +142,25 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothRemoteGattCharacteristic virtual void ReadRemoteCharacteristic(ValueCallback callback, ErrorCallback error_callback) = 0; + // Sends a write request to a remote characteristic with the value |value| + // using the specified |write_type|. |callback| is called to signal success + // and |error_callback| for failures. This method only applies to remote + // characteristics and will fail for those that are locally hosted. + virtual void WriteRemoteCharacteristic(const std::vector<uint8_t>& value, + WriteType write_type, + base::OnceClosure callback, + ErrorCallback error_callback) = 0; + + // DEPRECATED: Use WriteRemoteCharacteristic instead. This method remains + // for backward compatibility. // Sends a write request to a remote characteristic with the value |value|. // |callback| is called to signal success and |error_callback| for failures. // This method only applies to remote characteristics and will fail for those // that are locally hosted. - virtual void WriteRemoteCharacteristic(const std::vector<uint8_t>& value, - base::OnceClosure callback, - ErrorCallback error_callback) = 0; + virtual void DeprecatedWriteRemoteCharacteristic( + const std::vector<uint8_t>& value, + base::OnceClosure callback, + ErrorCallback error_callback) = 0; #if defined(OS_CHROMEOS) // Sends a prepare write request to a remote characteristic with the value @@ -157,15 +175,6 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothRemoteGattCharacteristic ErrorCallback error_callback) = 0; #endif - // Sends a write request to a remote characteristic with the value |value| - // without waiting for a response. This method returns false to signal - // failures. When attempting to write the remote characteristic true is - // returned without a guarantee of success. This method only applies to remote - // characteristics and will fail for those that are locally hosted. - // This method is currently implemented only on macOS. - // TODO(https://crbug.com/831524): Implement it on other platforms as well. - virtual bool WriteWithoutResponse(base::span<const uint8_t> value); - protected: using DescriptorMap = base::flat_map<std::string, diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_android.cc b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_android.cc index 395d1dbe209..fe92a2b85c8 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_android.cc +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_android.cc @@ -152,6 +152,7 @@ void BluetoothRemoteGattCharacteristicAndroid::ReadRemoteCharacteristic( void BluetoothRemoteGattCharacteristicAndroid::WriteRemoteCharacteristic( const std::vector<uint8_t>& value, + WriteType write_type, base::OnceClosure callback, ErrorCallback error_callback) { if (read_pending_ || write_pending_) { @@ -162,9 +163,48 @@ void BluetoothRemoteGattCharacteristicAndroid::WriteRemoteCharacteristic( return; } + AndroidWriteType android_write_type; + switch (write_type) { + case WriteType::kWithResponse: + android_write_type = AndroidWriteType::kDefault; + break; + case WriteType::kWithoutResponse: + android_write_type = AndroidWriteType::kNoResponse; + break; + } + + JNIEnv* env = AttachCurrentThread(); + if (!Java_ChromeBluetoothRemoteGattCharacteristic_writeRemoteCharacteristic( + env, j_characteristic_, base::android::ToJavaByteArray(env, value), + static_cast<int>(android_write_type))) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(std::move(error_callback), + BluetoothRemoteGattService::GATT_ERROR_FAILED)); + return; + } + + write_pending_ = true; + write_callback_ = std::move(callback); + write_error_callback_ = std::move(error_callback); +} + +void BluetoothRemoteGattCharacteristicAndroid:: + DeprecatedWriteRemoteCharacteristic(const std::vector<uint8_t>& value, + base::OnceClosure callback, + ErrorCallback error_callback) { + if (read_pending_ || write_pending_) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(std::move(error_callback), + BluetoothRemoteGattService::GATT_ERROR_IN_PROGRESS)); + return; + } + JNIEnv* env = AttachCurrentThread(); if (!Java_ChromeBluetoothRemoteGattCharacteristic_writeRemoteCharacteristic( - env, j_characteristic_, base::android::ToJavaByteArray(env, value))) { + env, j_characteristic_, base::android::ToJavaByteArray(env, value), + static_cast<int>(AndroidWriteType::kNone))) { base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, base::BindOnce(std::move(error_callback), diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_android.h b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_android.h index 63811345a6e..7710e77ce2a 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_android.h +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_android.h @@ -66,8 +66,13 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothRemoteGattCharacteristicAndroid void ReadRemoteCharacteristic(ValueCallback callback, ErrorCallback error_callback) override; void WriteRemoteCharacteristic(const std::vector<uint8_t>& value, + WriteType write_type, base::OnceClosure callback, ErrorCallback error_callback) override; + void DeprecatedWriteRemoteCharacteristic( + const std::vector<uint8_t>& value, + base::OnceClosure callback, + ErrorCallback error_callback) override; // Called when value changed event occurs. void OnChanged(JNIEnv* env, @@ -108,6 +113,15 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothRemoteGattCharacteristicAndroid ErrorCallback error_callback) override; private: + // Android API characteristic write type flags. + // https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html + enum class AndroidWriteType { + kNone = 0, + kNoResponse = 1 << 0, + kDefault = 1 << 1, + kSigned = 1 << 2, + }; + BluetoothRemoteGattCharacteristicAndroid( BluetoothAdapterAndroid* adapter, BluetoothRemoteGattServiceAndroid* service, diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h index 34e4a46769b..71510e2bddc 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h @@ -44,9 +44,13 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothRemoteGattCharacteristicMac void ReadRemoteCharacteristic(ValueCallback callback, ErrorCallback error_callback) override; void WriteRemoteCharacteristic(const std::vector<uint8_t>& value, + WriteType write_type, base::OnceClosure callback, ErrorCallback error_callback) override; - bool WriteWithoutResponse(base::span<const uint8_t> value) override; + void DeprecatedWriteRemoteCharacteristic( + const std::vector<uint8_t>& value, + base::OnceClosure callback, + ErrorCallback error_callback) override; protected: void SubscribeToNotifications(BluetoothRemoteGattDescriptor* ccc_descriptor, @@ -82,8 +86,6 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothRemoteGattCharacteristicMac bool IsReadable() const; // Returns true if the characteristic is writable. bool IsWritable() const; - // Returns true if the characteristic is writable without response. - bool IsWritableWithoutResponse() const; // Returns true if the characteristic supports notifications or indications. bool SupportsNotificationsOrIndications() const; // Returns the write type (with or without responses). diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm index 65020670281..ec869f70c2f 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_mac.mm @@ -5,6 +5,7 @@ #include "device/bluetooth/bluetooth_remote_gatt_characteristic_mac.h" #include "base/bind.h" +#include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/strings/sys_string_conversions.h" #include "base/threading/thread_task_runner_handle.h" @@ -154,6 +155,46 @@ void BluetoothRemoteGattCharacteristicMac::ReadRemoteCharacteristic( void BluetoothRemoteGattCharacteristicMac::WriteRemoteCharacteristic( const std::vector<uint8_t>& value, + WriteType write_type, + base::OnceClosure callback, + ErrorCallback error_callback) { + if (HasPendingRead() || HasPendingWrite()) { + DVLOG(1) << *this << ": Characteristic write already in progress."; + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(std::move(error_callback), + BluetoothRemoteGattService::GATT_ERROR_IN_PROGRESS)); + return; + } + DVLOG(1) << *this << ": Write characteristic."; + write_characteristic_value_callbacks_ = + std::make_pair(std::move(callback), std::move(error_callback)); + base::scoped_nsobject<NSData> nsdata_value( + [[NSData alloc] initWithBytes:value.data() length:value.size()]); + + CBCharacteristicWriteType cb_write_type; + switch (write_type) { + case WriteType::kWithResponse: + cb_write_type = CBCharacteristicWriteWithResponse; + break; + case WriteType::kWithoutResponse: + cb_write_type = CBCharacteristicWriteWithoutResponse; + break; + } + + [GetCBPeripheral() writeValue:nsdata_value + forCharacteristic:cb_characteristic_ + type:cb_write_type]; + if (cb_write_type == CBCharacteristicWriteWithoutResponse) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(&BluetoothRemoteGattCharacteristicMac::DidWriteValue, + weak_ptr_factory_.GetWeakPtr(), nil)); + } +} + +void BluetoothRemoteGattCharacteristicMac::DeprecatedWriteRemoteCharacteristic( + const std::vector<uint8_t>& value, base::OnceClosure callback, ErrorCallback error_callback) { if (!IsWritable()) { @@ -189,26 +230,6 @@ void BluetoothRemoteGattCharacteristicMac::WriteRemoteCharacteristic( } } -bool BluetoothRemoteGattCharacteristicMac::WriteWithoutResponse( - base::span<const uint8_t> value) { - if (!IsWritableWithoutResponse()) { - DVLOG(1) << *this << ": Characteristic not writable without response."; - return false; - } - if (HasPendingRead() || HasPendingWrite()) { - DVLOG(1) << *this << ": Characteristic write already in progress."; - return false; - } - - DVLOG(1) << *this << ": Write characteristic without response."; - base::scoped_nsobject<NSData> nsdata_value( - [[NSData alloc] initWithBytes:value.data() length:value.size()]); - [GetCBPeripheral() writeValue:nsdata_value - forCharacteristic:cb_characteristic_ - type:CBCharacteristicWriteWithoutResponse]; - return true; -} - void BluetoothRemoteGattCharacteristicMac::SubscribeToNotifications( BluetoothRemoteGattDescriptor* ccc_descriptor, base::OnceClosure callback, @@ -407,10 +428,6 @@ bool BluetoothRemoteGattCharacteristicMac::IsWritable() const { (properties & PROPERTY_WRITE_WITHOUT_RESPONSE); } -bool BluetoothRemoteGattCharacteristicMac::IsWritableWithoutResponse() const { - return (GetProperties() & PROPERTY_WRITE_WITHOUT_RESPONSE); -} - bool BluetoothRemoteGattCharacteristicMac::SupportsNotificationsOrIndications() const { BluetoothGattCharacteristic::Properties properties = GetProperties(); diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc index 3834b62cdd4..4581effa62f 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_unittest.cc @@ -8,6 +8,7 @@ #include <utility> #include "base/bind.h" +#include "base/logging.h" #include "base/run_loop.h" #include "base/stl_util.h" #include "base/test/bind_test_util.h" @@ -34,6 +35,7 @@ using testing::_; using testing::Invoke; +using WriteType = device::BluetoothRemoteGattCharacteristic::WriteType; namespace device { @@ -151,7 +153,7 @@ class BluetoothRemoteGattCharacteristicTest : // these two cases, this small utility function is added. bool IsClassicWin() { #if defined(OS_WIN) - return !GetParam(); + return !UsesNewBleImplementation(); #else return false; #endif @@ -419,7 +421,50 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); std::vector<uint8_t> empty_vector; + base::RunLoop loop; characteristic1_->WriteRemoteCharacteristic( + empty_vector, WriteType::kWithResponse, base::BindLambdaForTesting([&] { + EXPECT_EQ(1, gatt_write_characteristic_attempts_); + EXPECT_EQ(empty_vector, last_write_value_); + loop.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothRemoteGattService::GattErrorCode error_code) { + ADD_FAILURE() << "unexpected error: " << error_code; + loop.Quit(); + })); + SimulateGattCharacteristicWrite(characteristic1_); + loop.Run(); + + // Duplicate write reported from OS shouldn't cause a problem: + SimulateGattCharacteristicWrite(characteristic1_); + base::RunLoop().RunUntilIdle(); +} + +#if defined(OS_ANDROID) || defined(OS_MACOSX) +#define MAYBE_DeprecatedWriteRemoteCharacteristic_Empty \ + DeprecatedWriteRemoteCharacteristic_Empty +#else +#define MAYBE_DeprecatedWriteRemoteCharacteristic_Empty \ + DISABLED_DeprecatedWriteRemoteCharacteristic_Empty +#endif +// Tests DeprecatedWriteRemoteCharacteristic with empty value buffer. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + DeprecatedWriteRemoteCharacteristic_Empty) { +#else +TEST_F(BluetoothRemoteGattCharacteristicTest, + MAYBE_DeprecatedWriteRemoteCharacteristic_Empty) { +#endif + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( + BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + + std::vector<uint8_t> empty_vector; + characteristic1_->DeprecatedWriteRemoteCharacteristic( empty_vector, GetCallback(Call::EXPECTED), GetGattErrorCallback(Call::NOT_EXPECTED)); SimulateGattCharacteristicWrite(characteristic1_); @@ -496,8 +541,60 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); - bool write_error_callback_called = false; + base::RunLoop loop; characteristic1_->WriteRemoteCharacteristic( + {} /* value */, WriteType::kWithResponse, base::BindLambdaForTesting([&] { + ADD_FAILURE() << "unexpected success"; + loop.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothRemoteGattService::GattErrorCode error_code) { + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED, + error_code); + // Retrying Write should fail: + characteristic1_->WriteRemoteCharacteristic( + {} /* value */, WriteType::kWithResponse, + base::BindLambdaForTesting([&] { + ADD_FAILURE() << "unexpected success"; + loop.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothRemoteGattService::GattErrorCode error_code) { + EXPECT_EQ( + BluetoothRemoteGattService::GATT_ERROR_IN_PROGRESS, + error_code); + loop.Quit(); + })); + })); + + DeleteDevice(device_); // TODO(576906) delete only the characteristic. + loop.Run(); +} + +#if defined(OS_ANDROID) || defined(OS_MACOSX) +#define MAYBE_Retry_DeprecatedWriteRemoteCharacteristic_DuringDestruction_Fails \ + Retry_DeprecatedWriteRemoteCharacteristic_DuringDestruction_Fails +#else +#define MAYBE_Retry_DeprecatedWriteRemoteCharacteristic_DuringDestruction_Fails \ + DISABLED_Retry_DeprecatedWriteRemoteCharacteristic_DuringDestruction_Fails +#endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + Retry_DeprecatedWriteRemoteCharacteristic_DuringDestruction_Fails) { +#else +TEST_F( + BluetoothRemoteGattCharacteristicTest, + MAYBE_Retry_DeprecatedWriteRemoteCharacteristic_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_->DeprecatedWriteRemoteCharacteristic( {} /* value */, GetCallback(Call::NOT_EXPECTED), base::BindLambdaForTesting( [&](BluetoothRemoteGattService::GattErrorCode error_code) { @@ -505,7 +602,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, error_code); write_error_callback_called = true; // Retrying Write should fail: - characteristic1_->WriteRemoteCharacteristic( + characteristic1_->DeprecatedWriteRemoteCharacteristic( {} /* value */, GetCallback(Call::NOT_EXPECTED), GetGattErrorCallback(Call::EXPECTED)); })); @@ -617,8 +714,8 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, DISABLED_WriteRemoteCharacteristic_AfterDeleted #endif // Tests WriteRemoteCharacteristic completing after Chrome objects are deleted. -// macOS: Not applicable: This can never happen if CBPeripheral delegate is set -// to nil. +// 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) @@ -636,7 +733,54 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); std::vector<uint8_t> empty_vector; + base::RunLoop loop; characteristic1_->WriteRemoteCharacteristic( + empty_vector, WriteType::kWithResponse, base::BindLambdaForTesting([&] { + ADD_FAILURE() << "unexpected success"; + loop.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + EXPECT_EQ(BluetoothGattService::GATT_ERROR_FAILED, error_code); + loop.Quit(); + })); + + RememberCharacteristicForSubsequentAction(characteristic1_); + DeleteDevice(device_); // TODO(576906) delete only the characteristic. + + SimulateGattCharacteristicWrite(/* use remembered characteristic */ nullptr); + loop.Run(); +} + +#if defined(OS_ANDROID) +#define MAYBE_DeprecatedWriteRemoteCharacteristic_AfterDeleted \ + DeprecatedWriteRemoteCharacteristic_AfterDeleted +#else +#define MAYBE_DeprecatedWriteRemoteCharacteristic_AfterDeleted \ + DISABLED_DeprecatedWriteRemoteCharacteristic_AfterDeleted +#endif +// Tests DeprecatedWriteRemoteCharacteristic 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, + DeprecatedWriteRemoteCharacteristic_AfterDeleted) { +#else +TEST_F(BluetoothRemoteGattCharacteristicTest, + MAYBE_DeprecatedWriteRemoteCharacteristic_AfterDeleted) { +#endif + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( + BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + + std::vector<uint8_t> empty_vector; + characteristic1_->DeprecatedWriteRemoteCharacteristic( empty_vector, GetCallback(Call::NOT_EXPECTED), GetGattErrorCallback(Call::EXPECTED)); @@ -671,8 +815,65 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + base::RunLoop loop; std::vector<uint8_t> empty_vector; characteristic1_->WriteRemoteCharacteristic( + empty_vector, WriteType::kWithResponse, base::BindLambdaForTesting([&] { + ADD_FAILURE() << "unexpected success"; + loop.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothRemoteGattService::GattErrorCode error_code) { + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED, + error_code); + loop.Quit(); + })); + +// Set up for receiving a write response after disconnection. +// 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_ANDROID) + RememberCharacteristicForSubsequentAction(characteristic1_); +#endif // defined(OS_ANDROID) + + ASSERT_EQ(1u, adapter_->GetDevices().size()); + SimulateDeviceBreaksConnection(adapter_->GetDevices()[0]); + loop.Run(); + +// Dispatch write response after disconnection. See above explanation for why +// we don't do this in macOS and WinRT. +#if defined(OS_ANDROID) + SimulateGattCharacteristicWrite(/* use remembered characteristic */ nullptr); + base::RunLoop().RunUntilIdle(); +#endif // defined(OS_ANDROID) +} + +// TODO(crbug.com/663131): Enable test on windows when disconnection is +// implemented. +#if defined(OS_ANDROID) || defined(OS_MACOSX) +#define MAYBE_DeprecatedWriteRemoteCharacteristic_Disconnected \ + DeprecatedWriteRemoteCharacteristic_Disconnected +#else +#define MAYBE_DeprecatedWriteRemoteCharacteristic_Disconnected \ + DISABLED_DeprecatedWriteRemoteCharacteristic_Disconnected +#endif +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrtOnly, + DeprecatedWriteRemoteCharacteristic_Disconnected) { +#else +TEST_F(BluetoothRemoteGattCharacteristicTest, + MAYBE_DeprecatedWriteRemoteCharacteristic_Disconnected) { +#endif + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( + BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + + std::vector<uint8_t> empty_vector; + characteristic1_->DeprecatedWriteRemoteCharacteristic( empty_vector, GetCallback(Call::NOT_EXPECTED), GetGattErrorCallback(Call::EXPECTED)); @@ -780,8 +981,8 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, base::RunLoop().RunUntilIdle(); EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count()); -// TODO(https://crbug.com/699694): Remove this #if once the bug on Windows is -// fixed. + // TODO(https://crbug.com/699694): Remove this #if once the bug on Windows is + // fixed. if (IsClassicWin()) { EXPECT_FALSE(observer.last_gatt_characteristic_id().empty()); EXPECT_TRUE(observer.last_gatt_characteristic_uuid().IsValid()); @@ -812,9 +1013,57 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteRemoteCharacteristic) { TestBluetoothAdapterObserver observer(adapter_); + base::RunLoop loop; uint8_t values[] = {0, 1, 2, 3, 4, 0xf, 0xf0, 0xff}; std::vector<uint8_t> test_vector(values, values + base::size(values)); characteristic1_->WriteRemoteCharacteristic( + test_vector, WriteType::kWithResponse, base::BindLambdaForTesting([&] { + EXPECT_EQ(1, gatt_write_characteristic_attempts_); + + // 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_); + loop.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + ADD_FAILURE() << "unexpected error: " << error_code; + loop.Quit(); + })); + + SimulateGattCharacteristicWrite(characteristic1_); + loop.Run(); +} + +#if defined(OS_ANDROID) || defined(OS_MACOSX) +#define MAYBE_DeprecatedWriteRemoteCharacteristic \ + DeprecatedWriteRemoteCharacteristic +#else +#define MAYBE_DeprecatedWriteRemoteCharacteristic \ + DISABLED_DeprecatedWriteRemoteCharacteristic +#endif +// Tests DeprecatedWriteRemoteCharacteristic with non-empty value buffer. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + DeprecatedWriteRemoteCharacteristic) { +#else +TEST_F(BluetoothRemoteGattCharacteristicTest, + MAYBE_DeprecatedWriteRemoteCharacteristic) { +#endif + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( + BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + + TestBluetoothAdapterObserver observer(adapter_); + + uint8_t values[] = {0, 1, 2, 3, 4, 0xf, 0xf0, 0xff}; + std::vector<uint8_t> test_vector(values, values + base::size(values)); + characteristic1_->DeprecatedWriteRemoteCharacteristic( test_vector, GetCallback(Call::EXPECTED), GetGattErrorCallback(Call::NOT_EXPECTED)); @@ -899,9 +1148,69 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + base::RunLoop loop1; uint8_t values[] = {0, 1, 2, 3, 4, 0xf, 0xf0, 0xff}; std::vector<uint8_t> test_vector(values, values + base::size(values)); characteristic1_->WriteRemoteCharacteristic( + test_vector, WriteType::kWithResponse, base::BindLambdaForTesting([&] { + EXPECT_EQ(1, gatt_write_characteristic_attempts_); + EXPECT_EQ(test_vector, last_write_value_); + loop1.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + ADD_FAILURE() << "unexpected error" << error_code; + loop1.Quit(); + })); + + SimulateGattCharacteristicWrite(characteristic1_); + loop1.Run(); + + // Write again, with different value: + ResetEventCounts(); + base::RunLoop loop2; + std::vector<uint8_t> empty_vector; + characteristic1_->WriteRemoteCharacteristic( + empty_vector, WriteType::kWithResponse, base::BindLambdaForTesting([&] { + EXPECT_EQ(1, gatt_write_characteristic_attempts_); + EXPECT_EQ(empty_vector, last_write_value_); + loop2.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + ADD_FAILURE() << "unexpected error" << error_code; + loop2.Quit(); + })); + + SimulateGattCharacteristicWrite(characteristic1_); + loop2.Run(); +} + +#if defined(OS_ANDROID) || defined(OS_MACOSX) +#define MAYBE_DeprecatedWriteRemoteCharacteristic_Twice \ + DeprecatedWriteRemoteCharacteristic_Twice +#else +#define MAYBE_DeprecatedWriteRemoteCharacteristic_Twice \ + DISABLED_DeprecatedWriteRemoteCharacteristic_Twice +#endif +// Tests DeprecatedWriteRemoteCharacteristic multiple times. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + DeprecatedWriteRemoteCharacteristic_Twice) { +#else +TEST_F(BluetoothRemoteGattCharacteristicTest, + MAYBE_DeprecatedWriteRemoteCharacteristic_Twice) { +#endif + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( + BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + + uint8_t values[] = {0, 1, 2, 3, 4, 0xf, 0xf0, 0xff}; + std::vector<uint8_t> test_vector(values, values + base::size(values)); + characteristic1_->DeprecatedWriteRemoteCharacteristic( test_vector, GetCallback(Call::EXPECTED), GetGattErrorCallback(Call::NOT_EXPECTED)); @@ -915,7 +1224,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, // Write again, with different value: ResetEventCounts(); std::vector<uint8_t> empty_vector; - characteristic1_->WriteRemoteCharacteristic( + characteristic1_->DeprecatedWriteRemoteCharacteristic( empty_vector, GetCallback(Call::EXPECTED), GetGattErrorCallback(Call::NOT_EXPECTED)); @@ -999,9 +1308,76 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + base::RunLoop loop1; std::vector<uint8_t> test_vector1; test_vector1.push_back(111); characteristic1_->WriteRemoteCharacteristic( + test_vector1, WriteType::kWithResponse, base::BindLambdaForTesting([&] { + if (IsClassicWin()) { + EXPECT_EQ(test_vector1, last_write_value_); + } + loop1.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + ADD_FAILURE() << "unexpected error" << error_code; + loop1.Quit(); + })); + if (!IsClassicWin()) { + EXPECT_EQ(test_vector1, last_write_value_); + } + + base::RunLoop loop2; + std::vector<uint8_t> test_vector2; + test_vector2.push_back(222); + characteristic2_->WriteRemoteCharacteristic( + test_vector2, WriteType::kWithResponse, base::BindLambdaForTesting([&] { + if (IsClassicWin()) { + EXPECT_EQ(test_vector2, last_write_value_); + } + loop2.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + ADD_FAILURE() << "unexpected error" << error_code; + loop2.Quit(); + })); + if (!IsClassicWin()) { + EXPECT_EQ(test_vector2, last_write_value_); + } + + SimulateGattCharacteristicWrite(characteristic1_); + loop1.Run(); + + SimulateGattCharacteristicWrite(characteristic2_); + loop2.Run(); +} + +#if defined(OS_ANDROID) || defined(OS_MACOSX) +#define MAYBE_DeprecatedWriteRemoteCharacteristic_MultipleCharacteristics \ + DeprecatedWriteRemoteCharacteristic_MultipleCharacteristics +#else +#define MAYBE_DeprecatedWriteRemoteCharacteristic_MultipleCharacteristics \ + DISABLED_DeprecatedWriteRemoteCharacteristic_MultipleCharacteristics +#endif +// Tests DeprecatedWriteRemoteCharacteristic on two characteristics. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + DeprecatedWriteRemoteCharacteristic_MultipleCharacteristics) { +#else +TEST_F(BluetoothRemoteGattCharacteristicTest, + MAYBE_DeprecatedWriteRemoteCharacteristic_MultipleCharacteristics) { +#endif + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( + BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + + std::vector<uint8_t> test_vector1; + test_vector1.push_back(111); + characteristic1_->DeprecatedWriteRemoteCharacteristic( test_vector1, GetCallback(Call::EXPECTED), GetGattErrorCallback(Call::NOT_EXPECTED)); if (!IsClassicWin()) @@ -1009,7 +1385,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, std::vector<uint8_t> test_vector2; test_vector2.push_back(222); - characteristic2_->WriteRemoteCharacteristic( + characteristic2_->DeprecatedWriteRemoteCharacteristic( test_vector2, GetCallback(Call::EXPECTED), GetGattErrorCallback(Call::NOT_EXPECTED)); if (!IsClassicWin()) @@ -1110,10 +1486,67 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + base::RunLoop loop; std::vector<uint8_t> test_vector_1 = {0, 1, 2, 3, 4}; std::vector<uint8_t> test_vector_2 = {0xf, 0xf0, 0xff}; characteristic1_->WriteRemoteCharacteristic( + test_vector_1, WriteType::kWithResponse, base::BindLambdaForTesting([&] { + EXPECT_EQ(1, gatt_write_characteristic_attempts_); + EXPECT_EQ(test_vector_1, last_write_value_); + + characteristic1_->WriteRemoteCharacteristic( + test_vector_2, WriteType::kWithResponse, + base::BindLambdaForTesting([&] { + EXPECT_EQ(2, gatt_write_characteristic_attempts_); + EXPECT_EQ(test_vector_2, last_write_value_); + loop.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + ADD_FAILURE() << "unexpected error: " << error_code; + loop.Quit(); + })); + + SimulateGattCharacteristicWrite(characteristic1_); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + ADD_FAILURE() << "unexpected error: " << error_code; + loop.Quit(); + })); + + SimulateGattCharacteristicWrite(characteristic1_); + loop.Run(); +} + +#if defined(OS_ANDROID) || defined(OS_MACOSX) +#define MAYBE_RemoteCharacteristic_Nested_DeprecatedWrite_DeprecatedWrite \ + RemoteCharacteristic_Nested_DeprecatedWrite_DeprecatedWrite +#else +#define MAYBE_RemoteCharacteristic_Nested_DeprecatedWrite_DeprecatedWrite \ + DISABLED_RemoteCharacteristic_Nested_DeprecatedWrite_DeprecatedWrite +#endif +// Tests a nested DeprecatedWriteRemoteCharacteristic from within another +// DeprecatedWriteRemoteCharacteristic. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + RemoteCharacteristic_Nested_DeprecatedWrite_DeprecatedWrite) { +#else +TEST_F(BluetoothRemoteGattCharacteristicTest, + MAYBE_RemoteCharacteristic_Nested_DeprecatedWrite_DeprecatedWrite) { +#endif + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( + BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + + std::vector<uint8_t> test_vector_1 = {0, 1, 2, 3, 4}; + std::vector<uint8_t> test_vector_2 = {0xf, 0xf0, 0xff}; + + characteristic1_->DeprecatedWriteRemoteCharacteristic( test_vector_1, base::BindLambdaForTesting([&] { GetCallback(Call::EXPECTED).Run(); @@ -1122,7 +1555,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, EXPECT_EQ(0, error_callback_count_); EXPECT_EQ(test_vector_1, last_write_value_); - characteristic1_->WriteRemoteCharacteristic( + characteristic1_->DeprecatedWriteRemoteCharacteristic( test_vector_2, GetCallback(Call::EXPECTED), GetGattErrorCallback(Call::NOT_EXPECTED)); SimulateGattCharacteristicWrite(characteristic1_); @@ -1162,6 +1595,67 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, BluetoothRemoteGattCharacteristic::PROPERTY_READ | BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + base::RunLoop loop; + std::vector<uint8_t> test_vector_1 = {0, 1, 2, 3, 4}; + std::vector<uint8_t> test_vector_2 = {0xf, 0xf0, 0xff}; + + characteristic1_->ReadRemoteCharacteristic( + base::BindLambdaForTesting([&](const std::vector<uint8_t>& data) { + EXPECT_EQ(1, gatt_read_characteristic_attempts_); + EXPECT_EQ(0, gatt_write_characteristic_attempts_); + EXPECT_EQ(test_vector_1, data); + EXPECT_EQ(test_vector_1, characteristic1_->GetValue()); + + characteristic1_->WriteRemoteCharacteristic( + test_vector_2, WriteType::kWithResponse, + base::BindLambdaForTesting([&] { + EXPECT_EQ(1, gatt_read_characteristic_attempts_); + EXPECT_EQ(1, gatt_write_characteristic_attempts_); + EXPECT_EQ(test_vector_2, last_write_value_); + loop.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + ADD_FAILURE() << "unexpected error: " << error_code; + loop.Quit(); + })); + + SimulateGattCharacteristicWrite(characteristic1_); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + ADD_FAILURE() << "unexpected error: " << error_code; + loop.Quit(); + })); + + SimulateGattCharacteristicRead(characteristic1_, test_vector_1); + loop.Run(); +} + +#if defined(OS_ANDROID) || defined(OS_MACOSX) +#define MAYBE_RemoteCharacteristic_Nested_Read_DeprecatedWrite \ + RemoteCharacteristic_Nested_Read_DeprecatedWrite +#else +#define MAYBE_RemoteCharacteristic_Nested_Read_DeprecatedWrite \ + DISABLED_RemoteCharacteristic_Nested_Read_DeprecatedWrite +#endif +// Tests a nested DeprecatedWriteRemoteCharacteristic from within a +// ReadRemoteCharacteristic. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + RemoteCharacteristic_Nested_Read_DeprecatedWrite) { +#else +TEST_F(BluetoothRemoteGattCharacteristicTest, + MAYBE_RemoteCharacteristic_Nested_Read_DeprecatedWrite) { +#endif + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( + BluetoothRemoteGattCharacteristic::PROPERTY_READ | + BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + std::vector<uint8_t> test_vector_1 = {0, 1, 2, 3, 4}; std::vector<uint8_t> test_vector_2 = {0xf, 0xf0, 0xff}; @@ -1176,7 +1670,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, EXPECT_EQ(test_vector_1, last_read_value_); EXPECT_EQ(test_vector_1, characteristic1_->GetValue()); - characteristic1_->WriteRemoteCharacteristic( + characteristic1_->DeprecatedWriteRemoteCharacteristic( test_vector_2, GetCallback(Call::EXPECTED), GetGattErrorCallback(Call::NOT_EXPECTED)); SimulateGattCharacteristicWrite(characteristic1_); @@ -1216,10 +1710,69 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, BluetoothRemoteGattCharacteristic::PROPERTY_READ | BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + base::RunLoop loop; std::vector<uint8_t> test_vector_1 = {0, 1, 2, 3, 4}; std::vector<uint8_t> test_vector_2 = {0xf, 0xf0, 0xff}; characteristic1_->WriteRemoteCharacteristic( + test_vector_1, WriteType::kWithResponse, base::BindLambdaForTesting([&] { + EXPECT_EQ(0, gatt_read_characteristic_attempts_); + EXPECT_EQ(1, gatt_write_characteristic_attempts_); + EXPECT_EQ(test_vector_1, last_write_value_); + + characteristic1_->ReadRemoteCharacteristic( + base::BindLambdaForTesting([&](const std::vector<uint8_t>& data) { + EXPECT_EQ(1, gatt_read_characteristic_attempts_); + EXPECT_EQ(1, gatt_write_characteristic_attempts_); + EXPECT_EQ(test_vector_2, data); + EXPECT_EQ(test_vector_2, characteristic1_->GetValue()); + loop.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + ADD_FAILURE() << "unexpected error: " << error_code; + loop.Quit(); + })); + SimulateGattCharacteristicRead(characteristic1_, test_vector_2); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + ADD_FAILURE() << "unexpected error: " << error_code; + loop.Quit(); + })); + + SimulateGattCharacteristicWrite(characteristic1_); + loop.Run(); +} + +#if defined(OS_ANDROID) || defined(OS_MACOSX) +#define MAYBE_RemoteCharacteristic_Nested_DeprecatedWrite_Read \ + RemoteCharacteristic_Nested_DeprecatedWrite_Read +#else +#define MAYBE_RemoteCharacteristic_Nested_DeprecatedWrite_Read \ + DISABLED_RemoteCharacteristic_Nested_DeprecatedWrite_Read +#endif +// Tests a nested ReadRemoteCharacteristic from within a +// DeprecatedWriteRemoteCharacteristic. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + RemoteCharacteristic_Nested_DeprecatedWrite_Read) { +#else +TEST_F(BluetoothRemoteGattCharacteristicTest, + MAYBE_RemoteCharacteristic_Nested_DeprecatedWrite_Read) { +#endif + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( + BluetoothRemoteGattCharacteristic::PROPERTY_READ | + BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + + std::vector<uint8_t> test_vector_1 = {0, 1, 2, 3, 4}; + std::vector<uint8_t> test_vector_2 = {0xf, 0xf0, 0xff}; + + characteristic1_->DeprecatedWriteRemoteCharacteristic( test_vector_1, base::BindLambdaForTesting([&] { GetCallback(Call::EXPECTED).Run(); @@ -1297,8 +1850,47 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteError) { ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + base::RunLoop loop; std::vector<uint8_t> empty_vector; characteristic1_->WriteRemoteCharacteristic( + empty_vector, WriteType::kWithResponse, base::BindLambdaForTesting([&] { + ADD_FAILURE() << "unexpected success"; + loop.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_INVALID_LENGTH, + error_code); + loop.Quit(); + })); + SimulateGattCharacteristicWriteError( + characteristic1_, BluetoothRemoteGattService::GATT_ERROR_INVALID_LENGTH); + // Only the error above should be reported to the caller. + SimulateGattCharacteristicWriteError( + characteristic1_, BluetoothRemoteGattService::GATT_ERROR_FAILED); + loop.Run(); +} + +#if defined(OS_ANDROID) || defined(OS_MACOSX) +#define MAYBE_DeprecatedWriteError DeprecatedWriteError +#else +#define MAYBE_DeprecatedWriteError DISABLED_DeprecatedWriteError +#endif +// Tests DeprecatedWriteRemoteCharacteristic asynchronous error. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, DeprecatedWriteError) { +#else +TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_DeprecatedWriteError) { +#endif + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( + BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + + std::vector<uint8_t> empty_vector; + characteristic1_->DeprecatedWriteRemoteCharacteristic( empty_vector, GetCallback(Call::NOT_EXPECTED), GetGattErrorCallback(Call::EXPECTED)); SimulateGattCharacteristicWriteError( @@ -1358,8 +1950,54 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteSynchronousError) { ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate()); SimulateGattCharacteristicWriteWillFailSynchronouslyOnce(characteristic1_); + base::RunLoop loop1; std::vector<uint8_t> empty_vector; characteristic1_->WriteRemoteCharacteristic( + empty_vector, WriteType::kWithResponse, base::BindLambdaForTesting([&] { + ADD_FAILURE() << "unexpected success"; + loop1.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED, + error_code); + loop1.Quit(); + })); + loop1.Run(); + + // After failing once, can succeed: + ResetEventCounts(); + base::RunLoop loop2; + characteristic1_->WriteRemoteCharacteristic( + empty_vector, WriteType::kWithResponse, base::BindLambdaForTesting([&] { + SUCCEED(); + loop2.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + ADD_FAILURE() << "unexpected error: " << error_code; + loop2.Quit(); + })); + SimulateGattCharacteristicWrite(characteristic1_); + loop2.Run(); +} + +#if defined(OS_ANDROID) +#define MAYBE_DeprecatedWriteSynchronousError DeprecatedWriteSynchronousError +#else +#define MAYBE_DeprecatedWriteSynchronousError \ + DISABLED_DeprecatedWriteSynchronousError +#endif +// Tests DeprecatedWriteRemoteCharacteristic synchronous error. +// This test doesn't apply to macOS and WinRT since a synchronous API does not +// exist. +TEST_F(BluetoothRemoteGattCharacteristicTest, + MAYBE_DeprecatedWriteSynchronousError) { + ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate()); + + SimulateGattCharacteristicWriteWillFailSynchronouslyOnce(characteristic1_); + std::vector<uint8_t> empty_vector; + characteristic1_->DeprecatedWriteRemoteCharacteristic( empty_vector, GetCallback(Call::NOT_EXPECTED), GetGattErrorCallback(Call::EXPECTED)); EXPECT_EQ(0, gatt_write_characteristic_attempts_); @@ -1371,7 +2009,7 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteSynchronousError) { // After failing once, can succeed: ResetEventCounts(); - characteristic1_->WriteRemoteCharacteristic( + characteristic1_->DeprecatedWriteRemoteCharacteristic( empty_vector, GetCallback(Call::EXPECTED), GetGattErrorCallback(Call::NOT_EXPECTED)); EXPECT_EQ(1, gatt_write_characteristic_attempts_); @@ -1433,7 +2071,8 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, #define MAYBE_WriteRemoteCharacteristic_WritePending \ DISABLED_WriteRemoteCharacteristic_WritePending #endif -// Tests WriteRemoteCharacteristic error with a pending write operation. +// Tests WriteRemoteCharacteristic error with a pending write +// operation. #if defined(OS_WIN) TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, WriteRemoteCharacteristic_WritePending) { @@ -1448,11 +2087,67 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + base::RunLoop loop1; std::vector<uint8_t> empty_vector; characteristic1_->WriteRemoteCharacteristic( + empty_vector, WriteType::kWithResponse, base::BindLambdaForTesting([&] { + SUCCEED(); + loop1.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + ADD_FAILURE() << "unexpected error: " << error_code; + loop1.Quit(); + })); + base::RunLoop loop2; + characteristic1_->WriteRemoteCharacteristic( + empty_vector, WriteType::kWithResponse, base::BindLambdaForTesting([&] { + ADD_FAILURE() << "unexpected success"; + loop2.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_IN_PROGRESS, + error_code); + loop2.Quit(); + })); + + loop2.Run(); + + // Initial write should still succeed: + ResetEventCounts(); + SimulateGattCharacteristicWrite(characteristic1_); + loop1.Run(); +} + +#if defined(OS_ANDROID) || defined(OS_MACOSX) +#define MAYBE_DeprecatedWriteRemoteCharacteristic_WritePending \ + DeprecatedWriteRemoteCharacteristic_WritePending +#else +#define MAYBE_DeprecatedWriteRemoteCharacteristic_WritePending \ + DISABLED_DeprecatedWriteRemoteCharacteristic_WritePending +#endif +// Tests DeprecatedWriteRemoteCharacteristic error with a pending write +// operation. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + DeprecatedWriteRemoteCharacteristic_WritePending) { +#else +TEST_F(BluetoothRemoteGattCharacteristicTest, + MAYBE_DeprecatedWriteRemoteCharacteristic_WritePending) { +#endif + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( + BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + + std::vector<uint8_t> empty_vector; + characteristic1_->DeprecatedWriteRemoteCharacteristic( empty_vector, GetCallback(Call::EXPECTED), GetGattErrorCallback(Call::NOT_EXPECTED)); - characteristic1_->WriteRemoteCharacteristic( + characteristic1_->DeprecatedWriteRemoteCharacteristic( empty_vector, GetCallback(Call::NOT_EXPECTED), GetGattErrorCallback(Call::EXPECTED)); @@ -1494,8 +2189,64 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, BluetoothRemoteGattCharacteristic::PROPERTY_READ | BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + base::RunLoop loop1; + base::RunLoop loop2; std::vector<uint8_t> empty_vector; characteristic1_->WriteRemoteCharacteristic( + empty_vector, WriteType::kWithResponse, base::BindLambdaForTesting([&] { + SUCCEED(); + loop1.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + ADD_FAILURE() << "unexpected error: " << error_code; + loop1.Quit(); + })); + characteristic1_->ReadRemoteCharacteristic( + base::BindLambdaForTesting([&](const std::vector<uint8_t>& data) { + ADD_FAILURE() << "unexpected success"; + loop2.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_IN_PROGRESS, + error_code); + loop2.Quit(); + })); + + loop2.Run(); + + // Initial write should still succeed: + ResetEventCounts(); + SimulateGattCharacteristicWrite(characteristic1_); + loop1.Run(); +} + +#if defined(OS_ANDROID) || defined(OS_MACOSX) +#define MAYBE_ReadRemoteCharacteristic_DeprecatedWritePending \ + ReadRemoteCharacteristic_DeprecatedWritePending +#else +#define MAYBE_ReadRemoteCharacteristic_DeprecatedWritePending \ + DISABLED_ReadRemoteCharacteristic_DeprecatedWritePending +#endif +// Tests ReadRemoteCharacteristic error with a pending write operation. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + ReadRemoteCharacteristic_DeprecatedWritePending) { +#else +TEST_F(BluetoothRemoteGattCharacteristicTest, + MAYBE_ReadRemoteCharacteristic_DeprecatedWritePending) { +#endif + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( + BluetoothRemoteGattCharacteristic::PROPERTY_READ | + BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + + std::vector<uint8_t> empty_vector; + characteristic1_->DeprecatedWriteRemoteCharacteristic( empty_vector, GetCallback(Call::EXPECTED), GetGattErrorCallback(Call::NOT_EXPECTED)); characteristic1_->ReadRemoteCharacteristic( @@ -1540,11 +2291,67 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, BluetoothRemoteGattCharacteristic::PROPERTY_READ | BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + base::RunLoop loop1; + base::RunLoop loop2; + std::vector<uint8_t> empty_vector; + characteristic1_->ReadRemoteCharacteristic( + base::BindLambdaForTesting([&](const std::vector<uint8_t>& data) { + SUCCEED(); + loop1.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + ADD_FAILURE() << "unexpected error: " << error_code; + loop1.Quit(); + })); + characteristic1_->WriteRemoteCharacteristic( + empty_vector, WriteType::kWithResponse, base::BindLambdaForTesting([&] { + ADD_FAILURE() << "unexpected success"; + loop2.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_IN_PROGRESS, + error_code); + loop2.Quit(); + })); + loop2.Run(); + + // Initial read should still succeed: + ResetEventCounts(); + SimulateGattCharacteristicRead(characteristic1_, empty_vector); + loop1.Run(); +} + +#if defined(OS_ANDROID) || defined(OS_MACOSX) +#define MAYBE_DeprecatedWriteRemoteCharacteristic_ReadPending \ + DeprecatedWriteRemoteCharacteristic_ReadPending +#else +#define MAYBE_DeprecatedWriteRemoteCharacteristic_ReadPending \ + DISABLED_DeprecatedWriteRemoteCharacteristic_ReadPending +#endif +// Tests DeprecatedWriteRemoteCharacteristic error with a pending Read +// operation. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + DeprecatedWriteRemoteCharacteristic_ReadPending) { +#else +TEST_F(BluetoothRemoteGattCharacteristicTest, + MAYBE_DeprecatedWriteRemoteCharacteristic_ReadPending) { +#endif + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( + BluetoothRemoteGattCharacteristic::PROPERTY_READ | + BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + std::vector<uint8_t> empty_vector; characteristic1_->ReadRemoteCharacteristic( GetReadValueCallback(Call::EXPECTED), GetGattErrorCallback(Call::NOT_EXPECTED)); - characteristic1_->WriteRemoteCharacteristic( + characteristic1_->DeprecatedWriteRemoteCharacteristic( empty_vector, GetCallback(Call::NOT_EXPECTED), GetGattErrorCallback(Call::EXPECTED)); base::RunLoop().RunUntilIdle(); @@ -1656,8 +2463,60 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, TestBluetoothAdapterObserver observer(adapter_); + base::RunLoop loop; std::vector<uint8_t> write_value = {111}; characteristic1_->WriteRemoteCharacteristic( + write_value, WriteType::kWithResponse, base::BindLambdaForTesting([&] { + EXPECT_EQ(write_value, last_write_value_); + loop.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + ADD_FAILURE() << "unexpected error: " << error_code; + loop.Quit(); + })); + + std::vector<uint8_t> notification_value = {222}; + SimulateGattCharacteristicChanged(characteristic1_, notification_value); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(notification_value, characteristic1_->GetValue()); + EXPECT_EQ(1, observer.gatt_characteristic_value_changed_count()); + + observer.Reset(); + SimulateGattCharacteristicWrite(characteristic1_); + loop.Run(); +} + +#if defined(OS_ANDROID) || defined(OS_MACOSX) +#define MAYBE_Notification_During_DeprecatedWriteRemoteCharacteristic \ + Notification_During_DeprecatedWriteRemoteCharacteristic +#else +#define MAYBE_Notification_During_DeprecatedWriteRemoteCharacteristic \ + DISABLED_Notification_During_DeprecatedWriteRemoteCharacteristic +#endif +// Tests that a notification arriving during a pending write doesn't +// cause a crash. +#if defined(OS_WIN) +TEST_P(BluetoothRemoteGattCharacteristicTestWinrt, + Notification_During_DeprecatedWriteRemoteCharacteristic) { +#else +TEST_F(BluetoothRemoteGattCharacteristicTest, + MAYBE_Notification_During_DeprecatedWriteRemoteCharacteristic) { +#endif + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + ASSERT_NO_FATAL_FAILURE(StartNotifyBoilerplate( + BluetoothRemoteGattCharacteristic::PROPERTY_NOTIFY | + BluetoothRemoteGattCharacteristic::PROPERTY_WRITE, + NotifyValueState::NOTIFY)); + + TestBluetoothAdapterObserver observer(adapter_); + + std::vector<uint8_t> write_value = {111}; + characteristic1_->DeprecatedWriteRemoteCharacteristic( write_value, GetCallback(Call::EXPECTED), GetGattErrorCallback(Call::NOT_EXPECTED)); @@ -3344,9 +4203,47 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, MAYBE_WriteDuringDisconnect) { ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + base::RunLoop loop; SimulateGattDisconnection(device_); - // Do not yet call RunUntilIdle() to process the disconnect task. + // Do not yet call loop.Run() to process the disconnect task. characteristic1_->WriteRemoteCharacteristic( + std::vector<uint8_t>(), WriteType::kWithResponse, + base::BindLambdaForTesting([&] { + ADD_FAILURE() << "unexpected success"; + loop.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED, + error_code); + loop.Quit(); + })); + + loop.Run(); +} + +#if defined(OS_ANDROID) +#define MAYBE_DeprecatedWriteDuringDisconnect DeprecatedWriteDuringDisconnect +#else +#define MAYBE_DeprecatedWriteDuringDisconnect \ + DISABLED_DeprecatedWriteDuringDisconnect +#endif +// Tests that write requests after a device disconnects but before the +// disconnect task runs result in an error. +// macOS: Does not apply. All events arrive on the UI Thread. +// TODO(crbug.com/694102): Enable this test on Windows. +TEST_F(BluetoothRemoteGattCharacteristicTest, + MAYBE_DeprecatedWriteDuringDisconnect) { + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( + BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); + + SimulateGattDisconnection(device_); + // Do not yet call RunUntilIdle() to process the disconnect task. + characteristic1_->DeprecatedWriteRemoteCharacteristic( std::vector<uint8_t>(), GetCallback(Call::NOT_EXPECTED), GetGattErrorCallback(Call::EXPECTED)); @@ -3382,7 +4279,52 @@ TEST_F( ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( BluetoothRemoteGattCharacteristic::PROPERTY_WRITE_WITHOUT_RESPONSE)); + base::RunLoop loop; characteristic1_->WriteRemoteCharacteristic( + std::vector<uint8_t>(), WriteType::kWithoutResponse, + base::BindLambdaForTesting([&] { + ADD_FAILURE() << "unexpected success"; + loop.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED, + error_code); + loop.Quit(); + })); + SimulateDeviceBreaksConnection(adapter_->GetDevices()[0]); + + loop.Run(); +} + +#if defined(OS_MACOSX) +#define MAYBE_WriteWithoutResponseOnlyCharacteristic_DeprecatedWriteRemoteCharacteristicDuringDisconnect \ + WriteWithoutResponseOnlyCharacteristic_DeprecatedWriteRemoteCharacteristicDuringDisconnect +#else +#define MAYBE_WriteWithoutResponseOnlyCharacteristic_DeprecatedWriteRemoteCharacteristicDuringDisconnect \ + DISABLED_WriteWithoutResponseOnlyCharacteristic_DeprecatedWriteRemoteCharacteristicDuringDisconnect +#endif +// Tests that writing without response during a disconnect results in an error. +// 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_DeprecatedWriteRemoteCharacteristicDuringDisconnect) { +#else +TEST_F( + BluetoothRemoteGattCharacteristicTest, + MAYBE_WriteWithoutResponseOnlyCharacteristic_DeprecatedWriteRemoteCharacteristicDuringDisconnect) { +#endif + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( + BluetoothRemoteGattCharacteristic::PROPERTY_WRITE_WITHOUT_RESPONSE)); + + characteristic1_->DeprecatedWriteRemoteCharacteristic( std::vector<uint8_t>(), GetCallback(Call::NOT_EXPECTED), GetGattErrorCallback(Call::EXPECTED)); SimulateDeviceBreaksConnection(adapter_->GetDevices()[0]); @@ -3418,6 +4360,49 @@ TEST_F( base::RunLoop loop; characteristic1_->WriteRemoteCharacteristic( + std::vector<uint8_t>(), WriteType::kWithoutResponse, + base::BindLambdaForTesting([&] { + ADD_FAILURE() << "unexpected success"; + loop.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED, + error_code); + gatt_connections_[0]->Disconnect(); + loop.Quit(); + })); + SimulateDeviceBreaksConnection(adapter_->GetDevices()[0]); + loop.Run(); +} + +// Tests that closing the GATT connection when a characteristic value write +// fails due to a disconnect is safe. +#if defined(OS_ANDROID) || defined(OS_MACOSX) +#define MAYBE_DeprecatedWriteWithoutResponseOnlyCharacteristic_CloseConnectionDuringDisconnect \ + DeprecatedWriteWithoutResponseOnlyCharacteristic_CloseConnectionDuringDisconnect +#else +#define MAYBE_DeprecatedWriteWithoutResponseOnlyCharacteristic_CloseConnectionDuringDisconnect \ + DISABLED_DeprecatedWriteWithoutResponseOnlyCharacteristic_CloseConnectionDuringDisconnect +#endif +#if defined(OS_WIN) +TEST_P( + BluetoothRemoteGattCharacteristicTestWinrtOnly, + DeprecatedWriteWithoutResponseOnlyCharacteristic_CloseConnectionDuringDisconnect) { +#else +TEST_F( + BluetoothRemoteGattCharacteristicTest, + MAYBE_DeprecatedWriteWithoutResponseOnlyCharacteristic_CloseConnectionDuringDisconnect) { +#endif + if (!PlatformSupportsLowEnergy()) { + LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; + return; + } + ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( + BluetoothRemoteGattCharacteristic::PROPERTY_WRITE_WITHOUT_RESPONSE)); + + base::RunLoop loop; + characteristic1_->DeprecatedWriteRemoteCharacteristic( std::vector<uint8_t>(), GetCallback(Call::NOT_EXPECTED), base::BindLambdaForTesting( [&](BluetoothGattService::GattErrorCode error_code) { @@ -3451,33 +4436,40 @@ TEST_F( ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( BluetoothRemoteGattCharacteristic::PROPERTY_WRITE_WITHOUT_RESPONSE)); + base::RunLoop loop; characteristic1_->WriteRemoteCharacteristic( - std::vector<uint8_t>(), GetCallback(Call::NOT_EXPECTED), - GetGattErrorCallback(Call::EXPECTED)); + std::vector<uint8_t>(), WriteType::kWithoutResponse, + base::BindLambdaForTesting([&] { + ADD_FAILURE() << "unexpected success"; + loop.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED, + error_code); + loop.Quit(); + })); gatt_connections_[0]->Disconnect(); - base::RunLoop().RunUntilIdle(); - - EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED, - last_gatt_error_code_); + loop.Run(); SimulateGattDisconnection(device_); base::RunLoop().RunUntilIdle(); } #if defined(OS_MACOSX) -#define MAYBE_WriteWithoutResponseOnlyCharacteristic_DisconnectCalledBeforeWriteRemoteCharacteristic \ - WriteWithoutResponseOnlyCharacteristic_DisconnectCalledBeforeWriteRemoteCharacteristic +#define MAYBE_DeprecatedWriteWithoutResponseOnlyCharacteristic_DisconnectCalledDuringWriteRemoteCharacteristic \ + DeprecatedWriteWithoutResponseOnlyCharacteristic_DisconnectCalledDuringWriteRemoteCharacteristic #else -#define MAYBE_WriteWithoutResponseOnlyCharacteristic_DisconnectCalledBeforeWriteRemoteCharacteristic \ - DISABLED_WriteWithoutResponseOnlyCharacteristic_DisconnectCalledBeforeWriteRemoteCharacteristic +#define MAYBE_DeprecatedWriteWithoutResponseOnlyCharacteristic_DisconnectCalledDuringWriteRemoteCharacteristic \ + DISABLED_DeprecatedWriteWithoutResponseOnlyCharacteristic_DisconnectCalledDuringWriteRemoteCharacteristic #endif -// Tests that disconnecting right before a write without response results in an +// Tests that disconnecting right after a write without response results in an // error. // TODO(crbug.com/726534): Enable on other platforms depending on the resolution // of crbug.com/726534. TEST_F( BluetoothRemoteGattCharacteristicTest, - MAYBE_WriteWithoutResponseOnlyCharacteristic_DisconnectCalledBeforeWriteRemoteCharacteristic) { + MAYBE_DeprecatedWriteWithoutResponseOnlyCharacteristic_DisconnectCalledDuringWriteRemoteCharacteristic) { if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; @@ -3485,10 +4477,10 @@ TEST_F( ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( BluetoothRemoteGattCharacteristic::PROPERTY_WRITE_WITHOUT_RESPONSE)); - gatt_connections_[0]->Disconnect(); - characteristic1_->WriteRemoteCharacteristic( + characteristic1_->DeprecatedWriteRemoteCharacteristic( std::vector<uint8_t>(), GetCallback(Call::NOT_EXPECTED), GetGattErrorCallback(Call::EXPECTED)); + gatt_connections_[0]->Disconnect(); base::RunLoop().RunUntilIdle(); EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED, @@ -3499,145 +4491,78 @@ TEST_F( } #if defined(OS_MACOSX) -#define MAYBE_WriteWithoutResponse_PropertyNotPresent \ - WriteWithoutResponse_PropertyNotPresent -#else -#define MAYBE_WriteWithoutResponse_PropertyNotPresent \ - DISABLED_WriteWithoutResponse_PropertyNotPresent -#endif -// 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; - } - ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( - BluetoothRemoteGattCharacteristic::PROPERTY_WRITE)); - - std::vector<uint8_t> test_vector = {0, 1, 2, 3, 4, 0xf, 0xf0, 0xff}; - EXPECT_FALSE(characteristic1_->WriteWithoutResponse(test_vector)); -} - -#if defined(OS_MACOSX) -#define MAYBE_WriteWithoutResponse_PendingWrite \ - WriteWithoutResponse_PendingWrite -#else -#define MAYBE_WriteWithoutResponse_PendingWrite \ - DISABLED_WriteWithoutResponse_PendingWrite -#endif -// 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) { +#define MAYBE_WriteWithoutResponseOnlyCharacteristic_DisconnectCalledBeforeWriteRemoteCharacteristic \ + WriteWithoutResponseOnlyCharacteristic_DisconnectCalledBeforeWriteRemoteCharacteristic #else -TEST_F(BluetoothRemoteGattCharacteristicTest, - MAYBE_WriteWithoutResponse_PendingWrite) { +#define MAYBE_WriteWithoutResponseOnlyCharacteristic_DisconnectCalledBeforeWriteRemoteCharacteristic \ + DISABLED_WriteWithoutResponseOnlyCharacteristic_DisconnectCalledBeforeWriteRemoteCharacteristic #endif +// Tests that disconnecting right before a write without response results in an +// error. +// TODO(crbug.com/726534): Enable on other platforms depending on the resolution +// of crbug.com/726534. +TEST_F( + BluetoothRemoteGattCharacteristicTest, + MAYBE_WriteWithoutResponseOnlyCharacteristic_DisconnectCalledBeforeWriteRemoteCharacteristic) { if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; } ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( - BluetoothRemoteGattCharacteristic::PROPERTY_WRITE | BluetoothRemoteGattCharacteristic::PROPERTY_WRITE_WITHOUT_RESPONSE)); - std::vector<uint8_t> test_vector = {0, 1, 2, 3, 4, 0xf, 0xf0, 0xff}; + base::RunLoop loop; + gatt_connections_[0]->Disconnect(); characteristic1_->WriteRemoteCharacteristic( - test_vector, GetCallback(Call::EXPECTED), - GetGattErrorCallback(Call::NOT_EXPECTED)); - - // Explicitly make sure that the callback has not been invoked yet. Since - // WriteRemoteCharacteristic is still pending, this results in an error for - // WriteWithoutResponse. - EXPECT_EQ(0, callback_count_); - EXPECT_FALSE(characteristic1_->WriteWithoutResponse(test_vector)); + std::vector<uint8_t>(), WriteType::kWithoutResponse, + base::BindLambdaForTesting([&] { + ADD_FAILURE() << "unexpected success"; + loop.Quit(); + }), + base::BindLambdaForTesting( + [&](BluetoothGattService::GattErrorCode error_code) { + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED, + error_code); + loop.Quit(); + })); + loop.Run(); - // Test that the failed WriteWithoutReponse request does not interfere with - // the pending WriteRemoteCharacteristic request. - SimulateGattCharacteristicWrite(characteristic1_); + SimulateGattDisconnection(device_); base::RunLoop().RunUntilIdle(); - EXPECT_EQ(1, callback_count_); - EXPECT_EQ(test_vector, last_write_value_); } #if defined(OS_MACOSX) -#define MAYBE_WriteWithoutResponse_PendingRead WriteWithoutResponse_PendingRead +#define MAYBE_DeprecatedWriteWithoutResponseOnlyCharacteristic_DisconnectCalledBeforeWriteRemoteCharacteristic \ + DeprecatedWriteWithoutResponseOnlyCharacteristic_DisconnectCalledBeforeWriteRemoteCharacteristic #else -#define MAYBE_WriteWithoutResponse_PendingRead \ - DISABLED_WriteWithoutResponse_PendingRead -#endif -// 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) { +#define MAYBE_DeprecatedWriteWithoutResponseOnlyCharacteristic_DisconnectCalledBeforeWriteRemoteCharacteristic \ + DISABLED_DeprecatedWriteWithoutResponseOnlyCharacteristic_DisconnectCalledBeforeWriteRemoteCharacteristic #endif +// Tests that disconnecting right before a write without response results in an +// error. +// TODO(crbug.com/726534): Enable on other platforms depending on the resolution +// of crbug.com/726534. +TEST_F( + BluetoothRemoteGattCharacteristicTest, + MAYBE_DeprecatedWriteWithoutResponseOnlyCharacteristic_DisconnectCalledBeforeWriteRemoteCharacteristic) { if (!PlatformSupportsLowEnergy()) { LOG(WARNING) << "Low Energy Bluetooth unavailable, skipping unit test."; return; } ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( - BluetoothRemoteGattCharacteristic::PROPERTY_READ | BluetoothRemoteGattCharacteristic::PROPERTY_WRITE_WITHOUT_RESPONSE)); - characteristic1_->ReadRemoteCharacteristic( - GetReadValueCallback(Call::EXPECTED), - GetGattErrorCallback(Call::NOT_EXPECTED)); - - // Explicitly make sure that the callback has not been invoked yet. Since - // ReadRemoteCharacteristic is still pending, this results in an error for - // WriteWithoutResponse. - EXPECT_EQ(0, callback_count_); - std::vector<uint8_t> test_vector = {0, 1, 2, 3, 4, 0xf, 0xf0, 0xff}; - EXPECT_FALSE(characteristic1_->WriteWithoutResponse(test_vector)); - - // Test that the failed WriteWithoutReponse request does not interfere with - // the pending ReadRemoteCharacteristic request. - SimulateGattCharacteristicRead(characteristic1_, test_vector); + gatt_connections_[0]->Disconnect(); + characteristic1_->DeprecatedWriteRemoteCharacteristic( + std::vector<uint8_t>(), GetCallback(Call::NOT_EXPECTED), + GetGattErrorCallback(Call::EXPECTED)); base::RunLoop().RunUntilIdle(); - EXPECT_EQ(1, callback_count_); - EXPECT_EQ(test_vector, last_read_value_); -} -#if defined(OS_MACOSX) -#define MAYBE_WriteWithoutResponse_Success WriteWithoutResponse_Success -#else -#define MAYBE_WriteWithoutResponse_Success DISABLED_WriteWithoutResponse_Success -#endif -// 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; - } - ASSERT_NO_FATAL_FAILURE(FakeCharacteristicBoilerplate( - BluetoothRemoteGattCharacteristic::PROPERTY_WRITE_WITHOUT_RESPONSE)); + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED, + last_gatt_error_code_); - std::vector<uint8_t> test_vector = {1, 2, 3, 4}; - EXPECT_TRUE(characteristic1_->WriteWithoutResponse(test_vector)); - EXPECT_EQ(test_vector, last_write_value_); + SimulateGattDisconnection(device_); + base::RunLoop().RunUntilIdle(); } #if defined(OS_ANDROID) @@ -3843,20 +4768,19 @@ TEST_F(BluetoothRemoteGattCharacteristicTest, ExtraDidDiscoverDescriptorsCall) { #endif // defined(OS_MACOSX) #if defined(OS_WIN) -INSTANTIATE_TEST_SUITE_P( - All, - BluetoothRemoteGattCharacteristicTestWinrt, - ::testing::Bool()); +INSTANTIATE_TEST_SUITE_P(All, + BluetoothRemoteGattCharacteristicTestWinrt, + ::testing::ValuesIn(kBluetoothTestWinrtParamAll)); INSTANTIATE_TEST_SUITE_P( All, BluetoothRemoteGattCharacteristicTestWin32Only, - ::testing::Values(false)); + ::testing::ValuesIn(kBluetoothTestWinrtParamWin32Only)); INSTANTIATE_TEST_SUITE_P( All, BluetoothRemoteGattCharacteristicTestWinrtOnly, - ::testing::Values(true)); + ::testing::ValuesIn(kBluetoothTestWinrtParamWinrtOnly)); #endif // defined(OS_WIN) } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_win.cc b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_win.cc index dfe278ab735..54d4effdcdd 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_win.cc +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_win.cc @@ -159,6 +159,40 @@ void BluetoothRemoteGattCharacteristicWin::ReadRemoteCharacteristic( void BluetoothRemoteGattCharacteristicWin::WriteRemoteCharacteristic( const std::vector<uint8_t>& value, + WriteType write_type, + base::OnceClosure callback, + ErrorCallback error_callback) { + DCHECK(ui_task_runner_->RunsTasksInCurrentSequence()); + + if (characteristic_value_read_or_write_in_progress_) { + std::move(error_callback) + .Run(BluetoothRemoteGattService::GATT_ERROR_IN_PROGRESS); + return; + } + + ULONG flags; + switch (write_type) { + case WriteType::kWithResponse: + flags = BLUETOOTH_GATT_FLAG_NONE; + break; + case WriteType::kWithoutResponse: + flags = BLUETOOTH_GATT_FLAG_WRITE_WITHOUT_RESPONSE; + break; + } + + characteristic_value_read_or_write_in_progress_ = true; + write_characteristic_value_callbacks_ = + std::make_pair(std::move(callback), std::move(error_callback)); + task_manager_->PostWriteGattCharacteristicValue( + parent_service_->GetServicePath(), characteristic_info_.get(), value, + flags, + base::Bind(&BluetoothRemoteGattCharacteristicWin:: + OnWriteRemoteCharacteristicValueCallback, + weak_ptr_factory_.GetWeakPtr())); +} + +void BluetoothRemoteGattCharacteristicWin::DeprecatedWriteRemoteCharacteristic( + const std::vector<uint8_t>& value, base::OnceClosure callback, ErrorCallback error_callback) { DCHECK(ui_task_runner_->RunsTasksInCurrentSequence()); @@ -176,11 +210,17 @@ void BluetoothRemoteGattCharacteristicWin::WriteRemoteCharacteristic( return; } + ULONG flags = BLUETOOTH_GATT_FLAG_NONE; + if (!characteristic_info_->IsWritable) { + flags |= BLUETOOTH_GATT_FLAG_WRITE_WITHOUT_RESPONSE; + } + characteristic_value_read_or_write_in_progress_ = true; write_characteristic_value_callbacks_ = std::make_pair(std::move(callback), std::move(error_callback)); task_manager_->PostWriteGattCharacteristicValue( parent_service_->GetServicePath(), characteristic_info_.get(), value, + flags, base::Bind(&BluetoothRemoteGattCharacteristicWin:: OnWriteRemoteCharacteristicValueCallback, weak_ptr_factory_.GetWeakPtr())); diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_win.h b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_win.h index 56a3212b8b0..50fcaac8f3d 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_win.h +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_win.h @@ -45,8 +45,13 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothRemoteGattCharacteristicWin void ReadRemoteCharacteristic(ValueCallback callback, ErrorCallback error_callback) override; void WriteRemoteCharacteristic(const std::vector<uint8_t>& value, + WriteType write_type, base::OnceClosure callback, ErrorCallback error_callback) override; + void DeprecatedWriteRemoteCharacteristic( + const std::vector<uint8_t>& value, + base::OnceClosure callback, + ErrorCallback error_callback) override; // Update included descriptors. void Update(); diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_winrt.cc b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_winrt.cc index 8e340c5dbad..2e5a0a979e4 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_winrt.cc +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_winrt.cc @@ -46,6 +46,8 @@ using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: GattCommunicationStatus_Success; using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile::GattReadResult; using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: + GattWriteOption; +using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: GattWriteOption_WriteWithoutResponse; using ABI::Windows::Devices::Bluetooth::GenericAttributeProfile:: GattWriteOption_WriteWithResponse; @@ -203,17 +205,9 @@ void BluetoothRemoteGattCharacteristicWinrt::ReadRemoteCharacteristic( void BluetoothRemoteGattCharacteristicWinrt::WriteRemoteCharacteristic( const std::vector<uint8_t>& value, + WriteType write_type, base::OnceClosure callback, ErrorCallback error_callback) { - if (!(GetProperties() & PROPERTY_WRITE) && - !(GetProperties() & PROPERTY_WRITE_WITHOUT_RESPONSE)) { - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, - base::BindOnce(std::move(error_callback), - BluetoothRemoteGattService::GATT_ERROR_NOT_PERMITTED)); - return; - } - if (pending_read_callbacks_ || pending_write_callbacks_) { base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, @@ -246,13 +240,19 @@ void BluetoothRemoteGattCharacteristicWinrt::WriteRemoteCharacteristic( return; } + GattWriteOption write_option; + switch (write_type) { + case WriteType::kWithResponse: + write_option = GattWriteOption_WriteWithResponse; + break; + case WriteType::kWithoutResponse: + write_option = GattWriteOption_WriteWithoutResponse; + break; + } + ComPtr<IAsyncOperation<GattWriteResult*>> write_value_op; hr = characteristic_3->WriteValueWithResultAndOptionAsync( - buffer.Get(), - (GetProperties() & PROPERTY_WRITE) ? GattWriteOption_WriteWithResponse - : GattWriteOption_WriteWithoutResponse, - - &write_value_op); + buffer.Get(), write_option, &write_value_op); if (FAILED(hr)) { BLUETOOTH_LOG(DEBUG) << "GattCharacteristic::WriteValueWithResultAndOptionAsync failed: " @@ -284,6 +284,27 @@ void BluetoothRemoteGattCharacteristicWinrt::WriteRemoteCharacteristic( std::move(callback), std::move(error_callback)); } +void BluetoothRemoteGattCharacteristicWinrt:: + DeprecatedWriteRemoteCharacteristic(const std::vector<uint8_t>& value, + base::OnceClosure callback, + ErrorCallback error_callback) { + if (!(GetProperties() & PROPERTY_WRITE) && + !(GetProperties() & PROPERTY_WRITE_WITHOUT_RESPONSE)) { + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, + base::BindOnce(std::move(error_callback), + BluetoothRemoteGattService::GATT_ERROR_NOT_PERMITTED)); + return; + } + + WriteType write_type = (GetProperties() & PROPERTY_WRITE) + ? WriteType::kWithResponse + : WriteType::kWithoutResponse; + + WriteRemoteCharacteristic(value, write_type, std::move(callback), + std::move(error_callback)); +} + void BluetoothRemoteGattCharacteristicWinrt::UpdateDescriptors( BluetoothGattDiscovererWinrt* gatt_discoverer) { const auto* gatt_descriptors = @@ -311,56 +332,6 @@ void BluetoothRemoteGattCharacteristicWinrt::UpdateDescriptors( 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)) { - BLUETOOTH_LOG(DEBUG) << "As IGattCharacteristic3 failed: " - << logging::SystemErrorCodeToString(hr); - return false; - } - - ComPtr<IBuffer> buffer; - hr = base::win::CreateIBufferFromData(value.data(), value.size(), &buffer); - if (FAILED(hr)) { - BLUETOOTH_LOG(DEBUG) << "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)) { - BLUETOOTH_LOG(DEBUG) - << "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 = - base::win::PostAsyncResults(std::move(write_value_op), base::DoNothing()); - if (FAILED(hr)) { - BLUETOOTH_LOG(DEBUG) << "PostAsyncResults failed: " - << logging::SystemErrorCodeToString(hr); - return false; - } - - return true; -} - IGattCharacteristic* BluetoothRemoteGattCharacteristicWinrt::GetCharacteristicForTesting() { return characteristic_.Get(); diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_winrt.h b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_winrt.h index c1d7df05a32..1402fa2354c 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_winrt.h +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_characteristic_winrt.h @@ -48,9 +48,13 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothRemoteGattCharacteristicWinrt void ReadRemoteCharacteristic(ValueCallback callback, ErrorCallback error_callback) override; void WriteRemoteCharacteristic(const std::vector<uint8_t>& value, + WriteType write_type, base::OnceClosure callback, ErrorCallback error_callback) override; - bool WriteWithoutResponse(base::span<const uint8_t> value) override; + void DeprecatedWriteRemoteCharacteristic( + const std::vector<uint8_t>& value, + base::OnceClosure callback, + ErrorCallback error_callback) override; void UpdateDescriptors(BluetoothGattDiscovererWinrt* gatt_discoverer); diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_descriptor_unittest.cc b/chromium/device/bluetooth/bluetooth_remote_gatt_descriptor_unittest.cc index b0d14a15cd3..1a410cbc377 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_descriptor_unittest.cc +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_descriptor_unittest.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/logging.h" #include "base/run_loop.h" #include "base/stl_util.h" #include "build/build_config.h" @@ -999,7 +1000,7 @@ TEST_F(BluetoothRemoteGattDescriptorTest, ReadRemoteDescriptor_NSNumber) { INSTANTIATE_TEST_SUITE_P( All, BluetoothRemoteGattDescriptorTestWinrtOnly, - ::testing::Values(true)); + ::testing::ValuesIn(kBluetoothTestWinrtParamWinrtOnly)); #endif // defined(OS_WIN) } // namespace device diff --git a/chromium/device/bluetooth/bluetooth_remote_gatt_service_unittest.cc b/chromium/device/bluetooth/bluetooth_remote_gatt_service_unittest.cc index 70221d3bc1e..5ba4392be5e 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_service_unittest.cc +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_service_unittest.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/logging.h" #include "base/run_loop.h" #include "build/build_config.h" #include "device/bluetooth/bluetooth_gatt_service.h" @@ -415,7 +416,7 @@ TEST_F(BluetoothRemoteGattServiceTest, MAYBE_SimulateGattServiceRemove) { SimulateGattServiceRemoved(device->GetGattService(removed_service)); base::RunLoop().RunUntilIdle(); #if defined(OS_WIN) - if (!GetParam()) { + if (!UsesNewBleImplementation()) { // The GattServicesRemoved event is not implemented for WinRT. EXPECT_EQ(1, observer.gatt_service_removed_count()); } @@ -597,10 +598,9 @@ TEST_F(BluetoothRemoteGattServiceTest, ExtraDidDiscoverCharacteristicsCall) { #endif // defined(OS_MACOSX) #if defined(OS_WIN) -INSTANTIATE_TEST_SUITE_P( - All, - BluetoothRemoteGattServiceTestWinrt, - ::testing::Bool()); +INSTANTIATE_TEST_SUITE_P(All, + BluetoothRemoteGattServiceTestWinrt, + ::testing::ValuesIn(kBluetoothTestWinrtParamAll)); #endif // defined(OS_WIN) } // 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 e8c990dc7c2..75852935344 100644 --- a/chromium/device/bluetooth/bluetooth_remote_gatt_service_winrt.h +++ b/chromium/device/bluetooth/bluetooth_remote_gatt_service_winrt.h @@ -14,6 +14,7 @@ #include <string> #include <vector> +#include "base/logging.h" #include "base/macros.h" #include "device/bluetooth/bluetooth_export.h" #include "device/bluetooth/bluetooth_remote_gatt_characteristic_winrt.h" diff --git a/chromium/device/bluetooth/bluetooth_task_manager_win.cc b/chromium/device/bluetooth/bluetooth_task_manager_win.cc index 4f1883f4edc..55a425be257 100644 --- a/chromium/device/bluetooth/bluetooth_task_manager_win.cc +++ b/chromium/device/bluetooth/bluetooth_task_manager_win.cc @@ -12,6 +12,7 @@ #include <utility> #include "base/bind.h" +#include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/memory/ref_counted.h" #include "base/no_destructor.h" @@ -20,6 +21,7 @@ #include "base/strings/sys_string_conversions.h" #include "base/task/post_task.h" #include "base/task/thread_pool.h" +#include "base/threading/scoped_thread_priority.h" #include "device/bluetooth/bluetooth_classic_win.h" #include "device/bluetooth/bluetooth_device.h" #include "device/bluetooth/bluetooth_init_win.h" @@ -644,6 +646,10 @@ int BluetoothTaskManagerWin::DiscoverClassicDeviceServicesWorker( const GUID& protocol_uuid, bool search_cached_services_only, std::vector<std::unique_ptr<ServiceRecordState>>* service_record_states) { + // Mitigate the issues caused by loading DLLs on a background thread + // (http://crbug/973868). + SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY(); + // Bluetooth and WSAQUERYSET for Service Inquiry. See http://goo.gl/2v9pyt. WSAQUERYSET sdp_query; ZeroMemory(&sdp_query, sizeof(sdp_query)); @@ -869,6 +875,7 @@ void BluetoothTaskManagerWin::WriteGattCharacteristicValue( base::FilePath service_path, BTH_LE_GATT_CHARACTERISTIC characteristic, std::vector<uint8_t> new_value, + ULONG flags, const HResultCallback& callback) { ULONG length = (ULONG)(sizeof(ULONG) + new_value.size()); std::vector<UCHAR> data(length); @@ -880,7 +887,7 @@ void BluetoothTaskManagerWin::WriteGattCharacteristicValue( HRESULT hr = le_wrapper_->WriteCharacteristicValue( service_path, (PBTH_LE_GATT_CHARACTERISTIC)(&characteristic), - win_new_value); + win_new_value, flags); ui_task_runner_->PostTask(FROM_HERE, base::BindOnce(callback, hr)); } @@ -987,12 +994,14 @@ void BluetoothTaskManagerWin::PostWriteGattCharacteristicValue( const base::FilePath& service_path, const PBTH_LE_GATT_CHARACTERISTIC characteristic, const std::vector<uint8_t>& new_value, + ULONG flags, const HResultCallback& callback) { DCHECK(ui_task_runner_->RunsTasksInCurrentSequence()); bluetooth_task_runner_->PostTask( FROM_HERE, base::BindOnce(&BluetoothTaskManagerWin::WriteGattCharacteristicValue, - this, service_path, *characteristic, new_value, callback)); + this, service_path, *characteristic, new_value, flags, + callback)); } void BluetoothTaskManagerWin::PostRegisterGattCharacteristicValueChangedEvent( diff --git a/chromium/device/bluetooth/bluetooth_task_manager_win.h b/chromium/device/bluetooth/bluetooth_task_manager_win.h index de693e2ede5..bd7181e7977 100644 --- a/chromium/device/bluetooth/bluetooth_task_manager_win.h +++ b/chromium/device/bluetooth/bluetooth_task_manager_win.h @@ -186,6 +186,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothTaskManagerWin const base::FilePath& service_path, const PBTH_LE_GATT_CHARACTERISTIC characteristic, const std::vector<uint8_t>& new_value, + ULONG flags, const HResultCallback& callback); // Post a task to register to receive value changed notifications from @@ -319,6 +320,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothTaskManagerWin void WriteGattCharacteristicValue(base::FilePath service_path, BTH_LE_GATT_CHARACTERISTIC characteristic, std::vector<uint8_t> new_value, + ULONG flags, const HResultCallback& callback); void RegisterGattCharacteristicValueChangedEvent( base::FilePath service_path, diff --git a/chromium/device/bluetooth/bluez/bluetooth_adapter_bluez.cc b/chromium/device/bluetooth/bluez/bluetooth_adapter_bluez.cc index 6e6928e0ca2..bbea7dffa02 100644 --- a/chromium/device/bluetooth/bluez/bluetooth_adapter_bluez.cc +++ b/chromium/device/bluetooth/bluez/bluetooth_adapter_bluez.cc @@ -1523,6 +1523,8 @@ void BluetoothAdapterBlueZ::UpdateFilter( void BluetoothAdapterBlueZ::StartScanWithFilter( std::unique_ptr<device::BluetoothDiscoveryFilter> discovery_filter, DiscoverySessionResultCallback callback) { + DCHECK(discovery_filter.get()); + if (!IsPresent()) { std::move(callback).Run( true, UMABluetoothDiscoverySessionOutcome::ADAPTER_NOT_PRESENT); @@ -1532,39 +1534,16 @@ void BluetoothAdapterBlueZ::StartScanWithFilter( BLUETOOTH_LOG(EVENT) << __func__; auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback)); - - if (discovery_filter && !discovery_filter->IsDefault()) { - std::unique_ptr<BluetoothDiscoveryFilter> df( - new BluetoothDiscoveryFilter(device::BLUETOOTH_TRANSPORT_DUAL)); - df->CopyFrom(*discovery_filter); - SetDiscoveryFilter( - std::move(df), - base::BindRepeating( - &BluetoothAdapterBlueZ::OnPreSetDiscoveryFilter, - weak_ptr_factory_.GetWeakPtr(), - base::BindRepeating(copyable_callback, /*is_error=*/false, - UMABluetoothDiscoverySessionOutcome::SUCCESS), - base::BindRepeating(copyable_callback, true)), - base::BindOnce( - &BluetoothAdapterBlueZ::OnPreSetDiscoveryFilterError, - weak_ptr_factory_.GetWeakPtr(), - base::BindRepeating(copyable_callback, /*is_error=*/false, - UMABluetoothDiscoverySessionOutcome::SUCCESS), - base::BindOnce(copyable_callback, true))); - return; - } - - // This is the first request to start device discovery. - bluez::BluezDBusManager::Get()->GetBluetoothAdapterClient()->StartDiscovery( - object_path_, - base::BindOnce( - &BluetoothAdapterBlueZ::OnStartDiscovery, + SetDiscoveryFilter( + std::move(discovery_filter), + base::BindRepeating( + &BluetoothAdapterBlueZ::OnPreSetDiscoveryFilter, weak_ptr_factory_.GetWeakPtr(), base::BindRepeating(copyable_callback, /*is_error=*/false, UMABluetoothDiscoverySessionOutcome::SUCCESS), base::BindRepeating(copyable_callback, true)), base::BindOnce( - &BluetoothAdapterBlueZ::OnStartDiscoveryError, + &BluetoothAdapterBlueZ::OnPreSetDiscoveryFilterError, weak_ptr_factory_.GetWeakPtr(), base::BindRepeating(copyable_callback, /*is_error=*/false, UMABluetoothDiscoverySessionOutcome::SUCCESS), @@ -1607,6 +1586,8 @@ void BluetoothAdapterBlueZ::SetDiscoveryFilter( std::unique_ptr<BluetoothDiscoveryFilter> discovery_filter, const base::Closure& callback, DiscoverySessionErrorCallback error_callback) { + DCHECK(discovery_filter.get()); + if (!IsPresent()) { std::move(error_callback) .Run(UMABluetoothDiscoverySessionOutcome::ADAPTER_REMOVED); @@ -1614,36 +1595,32 @@ void BluetoothAdapterBlueZ::SetDiscoveryFilter( } bluez::BluetoothAdapterClient::DiscoveryFilter dbus_discovery_filter; + uint16_t pathloss; + int16_t rssi; + uint8_t transport; + std::set<device::BluetoothUUID> uuids; + + if (discovery_filter->GetPathloss(&pathloss)) + dbus_discovery_filter.pathloss = std::make_unique<uint16_t>(pathloss); + + if (discovery_filter->GetRSSI(&rssi)) + dbus_discovery_filter.rssi = std::make_unique<int16_t>(rssi); + + transport = discovery_filter->GetTransport(); + if (transport == device::BLUETOOTH_TRANSPORT_LE) { + dbus_discovery_filter.transport = std::make_unique<std::string>("le"); + } else if (transport == device::BLUETOOTH_TRANSPORT_CLASSIC) { + dbus_discovery_filter.transport = std::make_unique<std::string>("bredr"); + } else if (transport == device::BLUETOOTH_TRANSPORT_DUAL) { + dbus_discovery_filter.transport = std::make_unique<std::string>("auto"); + } - if (discovery_filter.get() && !discovery_filter->IsDefault()) { - uint16_t pathloss; - int16_t rssi; - uint8_t transport; - std::set<device::BluetoothUUID> uuids; - - if (discovery_filter->GetPathloss(&pathloss)) - dbus_discovery_filter.pathloss.reset(new uint16_t(pathloss)); - - if (discovery_filter->GetRSSI(&rssi)) - dbus_discovery_filter.rssi.reset(new int16_t(rssi)); - - transport = discovery_filter->GetTransport(); - if (transport == device::BLUETOOTH_TRANSPORT_LE) { - dbus_discovery_filter.transport.reset(new std::string("le")); - } else if (transport == device::BLUETOOTH_TRANSPORT_CLASSIC) { - dbus_discovery_filter.transport.reset(new std::string("bredr")); - } else if (transport == device::BLUETOOTH_TRANSPORT_DUAL) { - dbus_discovery_filter.transport.reset(new std::string("auto")); - } - - discovery_filter->GetUUIDs(uuids); - if (uuids.size()) { - dbus_discovery_filter.uuids = std::unique_ptr<std::vector<std::string>>( - new std::vector<std::string>); + discovery_filter->GetUUIDs(uuids); + if (uuids.size()) { + dbus_discovery_filter.uuids = std::make_unique<std::vector<std::string>>(); - for (const auto& it : uuids) - dbus_discovery_filter.uuids.get()->push_back(it.value()); - } + for (const auto& it : uuids) + dbus_discovery_filter.uuids.get()->push_back(it.value()); } auto copyable_error_callback = diff --git a/chromium/device/bluetooth/bluez/bluetooth_device_bluez.cc b/chromium/device/bluetooth/bluez/bluetooth_device_bluez.cc index fadcb54401f..2d060a3424d 100644 --- a/chromium/device/bluetooth/bluez/bluetooth_device_bluez.cc +++ b/chromium/device/bluetooth/bluez/bluetooth_device_bluez.cc @@ -853,11 +853,13 @@ void BluetoothDeviceBlueZ::UpdateGattServices( // Add all previously unknown GATT services associated with the device. GattServiceAdded(service_path); - // If the service does not belong in this device, there is nothing left to - // do. + // |service_paths| includes all services for all devices, not just this + // device. GetGattService() will return nullptr for services belonging + // to other devices, so we skip those and keep looking for services that + // belong to this device. BluetoothRemoteGattService* service = GetGattService(service_path.value()); if (service == nullptr) { - return; + continue; } // Notify of GATT discovery complete if we haven't before. diff --git a/chromium/device/bluetooth/bluez/bluetooth_gatt_bluez_unittest.cc b/chromium/device/bluetooth/bluez/bluetooth_gatt_bluez_unittest.cc index 1f81414ef63..b59f4ace1d4 100644 --- a/chromium/device/bluetooth/bluez/bluetooth_gatt_bluez_unittest.cc +++ b/chromium/device/bluetooth/bluez/bluetooth_gatt_bluez_unittest.cc @@ -50,6 +50,7 @@ using device::BluetoothUUID; using device::TestBluetoothAdapterObserver; using UUIDSet = device::BluetoothDevice::UUIDSet; +using WriteType = device::BluetoothRemoteGattCharacteristic::WriteType; typedef std::unordered_map<device::BluetoothDevice*, device::BluetoothDevice::UUIDSet> @@ -96,7 +97,7 @@ void AddDeviceFilterWithUUID(BluetoothDiscoveryFilter* filter, class BluetoothGattBlueZTest : public testing::Test { public: BluetoothGattBlueZTest() - : fake_bluetooth_gatt_service_client_(NULL), + : fake_bluetooth_gatt_service_client_(nullptr), success_callback_count_(0), error_callback_count_(0) {} @@ -1027,7 +1028,7 @@ TEST_F(BluetoothGattBlueZTest, GattCharacteristicValue) { characteristic->GetIdentifier()); EXPECT_EQ(kHeartRateMeasurementUUID, characteristic->GetUUID()); characteristic->WriteRemoteCharacteristic( - write_value, + write_value, WriteType::kWithResponse, base::BindOnce(&BluetoothGattBlueZTest::SuccessCallback, base::Unretained(this)), base::BindOnce(&BluetoothGattBlueZTest::ServiceErrorCallback, @@ -1050,7 +1051,7 @@ TEST_F(BluetoothGattBlueZTest, GattCharacteristicValue) { characteristic->GetIdentifier()); EXPECT_EQ(kBodySensorLocationUUID, characteristic->GetUUID()); characteristic->WriteRemoteCharacteristic( - write_value, + write_value, WriteType::kWithResponse, base::BindOnce(&BluetoothGattBlueZTest::SuccessCallback, base::Unretained(this)), base::BindOnce(&BluetoothGattBlueZTest::ServiceErrorCallback, @@ -1077,7 +1078,7 @@ TEST_F(BluetoothGattBlueZTest, GattCharacteristicValue) { characteristic->GetIdentifier()); EXPECT_EQ(kHeartRateControlPointUUID, characteristic->GetUUID()); characteristic->WriteRemoteCharacteristic( - write_value, + write_value, WriteType::kWithResponse, base::BindOnce(&BluetoothGattBlueZTest::SuccessCallback, base::Unretained(this)), base::BindOnce(&BluetoothGattBlueZTest::ServiceErrorCallback, @@ -1095,7 +1096,7 @@ TEST_F(BluetoothGattBlueZTest, GattCharacteristicValue) { invalid_write_length.push_back(0x01); invalid_write_length.push_back(0x00); characteristic->WriteRemoteCharacteristic( - invalid_write_length, + invalid_write_length, WriteType::kWithResponse, base::BindOnce(&BluetoothGattBlueZTest::SuccessCallback, base::Unretained(this)), base::BindOnce(&BluetoothGattBlueZTest::ServiceErrorCallback, @@ -1109,6 +1110,227 @@ TEST_F(BluetoothGattBlueZTest, GattCharacteristicValue) { std::vector<uint8_t> invalid_write_value; invalid_write_value.push_back(0x02); characteristic->WriteRemoteCharacteristic( + invalid_write_value, WriteType::kWithResponse, + base::BindOnce(&BluetoothGattBlueZTest::SuccessCallback, + base::Unretained(this)), + base::BindOnce(&BluetoothGattBlueZTest::ServiceErrorCallback, + base::Unretained(this))); + EXPECT_EQ(1, success_callback_count_); + EXPECT_EQ(4, error_callback_count_); + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_FAILED, last_service_error_); + EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count()); + + // Issue a read request. + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_->GetBodySensorLocationPath() + .value()); + ASSERT_TRUE(characteristic); + EXPECT_EQ( + fake_bluetooth_gatt_characteristic_client_->GetBodySensorLocationPath() + .value(), + characteristic->GetIdentifier()); + EXPECT_EQ(kBodySensorLocationUUID, characteristic->GetUUID()); + characteristic->ReadRemoteCharacteristic( + base::BindOnce(&BluetoothGattBlueZTest::ValueCallback, + base::Unretained(this)), + base::BindOnce(&BluetoothGattBlueZTest::ServiceErrorCallback, + base::Unretained(this))); + EXPECT_EQ(2, success_callback_count_); + EXPECT_EQ(4, error_callback_count_); + EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count()); + EXPECT_TRUE(ValuesEqual(characteristic->GetValue(), last_read_value_)); + + // Test long-running actions. + fake_bluetooth_gatt_characteristic_client_->SetExtraProcessing(1); + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_->GetBodySensorLocationPath() + .value()); + ASSERT_TRUE(characteristic); + EXPECT_EQ( + fake_bluetooth_gatt_characteristic_client_->GetBodySensorLocationPath() + .value(), + characteristic->GetIdentifier()); + EXPECT_EQ(kBodySensorLocationUUID, characteristic->GetUUID()); + characteristic->ReadRemoteCharacteristic( + base::BindOnce(&BluetoothGattBlueZTest::ValueCallback, + base::Unretained(this)), + base::BindOnce(&BluetoothGattBlueZTest::ServiceErrorCallback, + base::Unretained(this))); + + // Callback counts shouldn't change, this one will be delayed until after + // tne next one. + EXPECT_EQ(2, success_callback_count_); + EXPECT_EQ(4, error_callback_count_); + EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count()); + + // Next read should error because IN_PROGRESS + characteristic->ReadRemoteCharacteristic( + base::BindOnce(&BluetoothGattBlueZTest::ValueCallback, + base::Unretained(this)), + base::BindOnce(&BluetoothGattBlueZTest::ServiceErrorCallback, + base::Unretained(this))); + EXPECT_EQ(5, error_callback_count_); + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_IN_PROGRESS, + last_service_error_); + + // But previous call finished. + EXPECT_EQ(3, success_callback_count_); + EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count()); + EXPECT_TRUE(ValuesEqual(characteristic->GetValue(), last_read_value_)); + fake_bluetooth_gatt_characteristic_client_->SetExtraProcessing(0); + + // Test unauthorized actions. + fake_bluetooth_gatt_characteristic_client_->SetAuthorized(false); + characteristic->ReadRemoteCharacteristic( + base::BindOnce(&BluetoothGattBlueZTest::ValueCallback, + base::Unretained(this)), + base::BindOnce(&BluetoothGattBlueZTest::ServiceErrorCallback, + base::Unretained(this))); + EXPECT_EQ(3, success_callback_count_); + EXPECT_EQ(6, error_callback_count_); + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_NOT_AUTHORIZED, + last_service_error_); + EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count()); + fake_bluetooth_gatt_characteristic_client_->SetAuthorized(true); + + // Test unauthenticated / needs login. + fake_bluetooth_gatt_characteristic_client_->SetAuthenticated(false); + characteristic->ReadRemoteCharacteristic( + base::BindOnce(&BluetoothGattBlueZTest::ValueCallback, + base::Unretained(this)), + base::BindOnce(&BluetoothGattBlueZTest::ServiceErrorCallback, + base::Unretained(this))); + EXPECT_EQ(3, success_callback_count_); + EXPECT_EQ(7, error_callback_count_); + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_NOT_PAIRED, + last_service_error_); + EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count()); + fake_bluetooth_gatt_characteristic_client_->SetAuthenticated(true); +} + +TEST_F(BluetoothGattBlueZTest, DeprecatedGattCharacteristicValue) { + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(bluez::FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(bluez::FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = + adapter_->GetDevice(bluez::FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + + TestBluetoothAdapterObserver observer(adapter_); + + // Expose the fake Heart Rate service. This will asynchronously expose + // characteristics. + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(bluez::FakeBluetoothDeviceClient::kLowEnergyPath)); + ASSERT_EQ(1, observer.gatt_service_added_count()); + + BluetoothRemoteGattService* service = + device->GetGattService(observer.last_gatt_service_id()); + + EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count()); + + // Run the message loop so that the characteristics appear. + base::RunLoop().Run(); + + // Issue write request to non-writable characteristics. + observer.Reset(); + + std::vector<uint8_t> write_value; + write_value.push_back(0x01); + BluetoothRemoteGattCharacteristic* characteristic = + service->GetCharacteristic(fake_bluetooth_gatt_characteristic_client_ + ->GetHeartRateMeasurementPath() + .value()); + ASSERT_TRUE(characteristic); + EXPECT_FALSE(characteristic->IsNotifying()); + EXPECT_EQ( + fake_bluetooth_gatt_characteristic_client_->GetHeartRateMeasurementPath() + .value(), + characteristic->GetIdentifier()); + EXPECT_EQ(kHeartRateMeasurementUUID, characteristic->GetUUID()); + characteristic->DeprecatedWriteRemoteCharacteristic( + write_value, + base::BindOnce(&BluetoothGattBlueZTest::SuccessCallback, + base::Unretained(this)), + base::BindOnce(&BluetoothGattBlueZTest::ServiceErrorCallback, + base::Unretained(this))); + EXPECT_TRUE(observer.last_gatt_characteristic_id().empty()); + EXPECT_FALSE(observer.last_gatt_characteristic_uuid().IsValid()); + EXPECT_EQ(0, success_callback_count_); + EXPECT_EQ(1, error_callback_count_); + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_NOT_SUPPORTED, + last_service_error_); + EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count()); + + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_->GetBodySensorLocationPath() + .value()); + ASSERT_TRUE(characteristic); + EXPECT_EQ( + fake_bluetooth_gatt_characteristic_client_->GetBodySensorLocationPath() + .value(), + characteristic->GetIdentifier()); + EXPECT_EQ(kBodySensorLocationUUID, characteristic->GetUUID()); + characteristic->DeprecatedWriteRemoteCharacteristic( + write_value, + base::BindOnce(&BluetoothGattBlueZTest::SuccessCallback, + base::Unretained(this)), + base::BindOnce(&BluetoothGattBlueZTest::ServiceErrorCallback, + base::Unretained(this))); + EXPECT_TRUE(observer.last_gatt_characteristic_id().empty()); + EXPECT_FALSE(observer.last_gatt_characteristic_uuid().IsValid()); + EXPECT_EQ(0, success_callback_count_); + EXPECT_EQ(2, error_callback_count_); + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_NOT_PERMITTED, + last_service_error_); + EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count()); + + // Issue write request to writable characteristic. The "Body Sensor Location" + // characteristic does not send notifications and WriteValue does not result + // in a CharacteristicValueChanged event, thus no such event should be + // received. + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_->GetHeartRateControlPointPath() + .value()); + ASSERT_TRUE(characteristic); + EXPECT_EQ( + fake_bluetooth_gatt_characteristic_client_->GetHeartRateControlPointPath() + .value(), + characteristic->GetIdentifier()); + EXPECT_EQ(kHeartRateControlPointUUID, characteristic->GetUUID()); + characteristic->DeprecatedWriteRemoteCharacteristic( + write_value, + base::BindOnce(&BluetoothGattBlueZTest::SuccessCallback, + base::Unretained(this)), + base::BindOnce(&BluetoothGattBlueZTest::ServiceErrorCallback, + base::Unretained(this))); + EXPECT_TRUE(observer.last_gatt_characteristic_id().empty()); + EXPECT_FALSE(observer.last_gatt_characteristic_uuid().IsValid()); + EXPECT_EQ(1, success_callback_count_); + EXPECT_EQ(2, error_callback_count_); + EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count()); + + // Issue some invalid write requests to the characteristic. + // The value should still not change. + + std::vector<uint8_t> invalid_write_length; + invalid_write_length.push_back(0x01); + invalid_write_length.push_back(0x00); + characteristic->DeprecatedWriteRemoteCharacteristic( + invalid_write_length, + base::BindOnce(&BluetoothGattBlueZTest::SuccessCallback, + base::Unretained(this)), + base::BindOnce(&BluetoothGattBlueZTest::ServiceErrorCallback, + base::Unretained(this))); + EXPECT_EQ(1, success_callback_count_); + EXPECT_EQ(3, error_callback_count_); + EXPECT_EQ(BluetoothRemoteGattService::GATT_ERROR_INVALID_LENGTH, + last_service_error_); + EXPECT_EQ(0, observer.gatt_characteristic_value_changed_count()); + + std::vector<uint8_t> invalid_write_value; + invalid_write_value.push_back(0x02); + characteristic->DeprecatedWriteRemoteCharacteristic( invalid_write_value, base::BindOnce(&BluetoothGattBlueZTest::SuccessCallback, base::Unretained(this)), @@ -1288,12 +1510,63 @@ TEST_F(BluetoothGattBlueZTest, GattCharacteristicValue_Nested_Write_Write) { std::vector<uint8_t> write_value = {0x01}; characteristic->WriteRemoteCharacteristic( - write_value, base::BindLambdaForTesting([&] { + write_value, WriteType::kWithResponse, base::BindLambdaForTesting([&] { SuccessCallback(); EXPECT_EQ(1, success_callback_count_); EXPECT_EQ(0, error_callback_count_); characteristic->WriteRemoteCharacteristic( + write_value, WriteType::kWithResponse, + base::BindOnce(&BluetoothGattBlueZTest::SuccessCallback, + base::Unretained(this)), + base::BindOnce(&BluetoothGattBlueZTest::ServiceErrorCallback, + base::Unretained(this))); + }), + base::BindOnce(&BluetoothGattBlueZTest::ServiceErrorCallback, + base::Unretained(this))); + EXPECT_EQ(2, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); +} + +// Test a write request issued from the success callback of another write +// request. +TEST_F(BluetoothGattBlueZTest, + GattCharacteristicValue_Nested_DeprecatedWrite_DeprecatedWrite) { + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(bluez::FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(bluez::FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = + adapter_->GetDevice(bluez::FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + + TestBluetoothAdapterObserver observer(adapter_); + + // Expose the fake Heart Rate service. This will asynchronously expose + // characteristics. + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(bluez::FakeBluetoothDeviceClient::kLowEnergyPath)); + ASSERT_EQ(1, observer.gatt_service_added_count()); + + BluetoothRemoteGattService* service = + device->GetGattService(observer.last_gatt_service_id()); + + // Run the message loop so that the characteristics appear. + base::RunLoop().Run(); + + // Obtain writable Heart Rate Control Point characteristic. + BluetoothRemoteGattCharacteristic* characteristic = + service->GetCharacteristic(fake_bluetooth_gatt_characteristic_client_ + ->GetHeartRateControlPointPath() + .value()); + + std::vector<uint8_t> write_value = {0x01}; + characteristic->DeprecatedWriteRemoteCharacteristic( + write_value, base::BindLambdaForTesting([&] { + SuccessCallback(); + EXPECT_EQ(1, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + + characteristic->DeprecatedWriteRemoteCharacteristic( write_value, base::BindOnce(&BluetoothGattBlueZTest::SuccessCallback, base::Unretained(this)), @@ -1349,6 +1622,62 @@ TEST_F(BluetoothGattBlueZTest, GattCharacteristicValue_Nested_Read_Write) { .value()); characteristic->WriteRemoteCharacteristic( + std::vector<uint8_t>({0x01}), WriteType::kWithResponse, + base::BindOnce(&BluetoothGattBlueZTest::SuccessCallback, + base::Unretained(this)), + base::BindOnce(&BluetoothGattBlueZTest::ServiceErrorCallback, + base::Unretained(this))); + }), + base::BindOnce(&BluetoothGattBlueZTest::ServiceErrorCallback, + base::Unretained(this))); + EXPECT_EQ(2, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); +} + +// Test a write request issued from the success callback of a read request. +TEST_F(BluetoothGattBlueZTest, + GattCharacteristicValue_Nested_Read_DeprecatedWrite) { + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(bluez::FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(bluez::FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = + adapter_->GetDevice(bluez::FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + + TestBluetoothAdapterObserver observer(adapter_); + + // Expose the fake Heart Rate service. This will asynchronously expose + // characteristics. + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(bluez::FakeBluetoothDeviceClient::kLowEnergyPath)); + ASSERT_EQ(1, observer.gatt_service_added_count()); + + BluetoothRemoteGattService* service = + device->GetGattService(observer.last_gatt_service_id()); + + // Run the message loop so that the characteristics appear. + base::RunLoop().Run(); + + // Obtain readable Body Sensor Location characteristic. + BluetoothRemoteGattCharacteristic* characteristic = + service->GetCharacteristic(fake_bluetooth_gatt_characteristic_client_ + ->GetBodySensorLocationPath() + .value()); + + characteristic->ReadRemoteCharacteristic( + base::BindLambdaForTesting([&](const std::vector<uint8_t>& data) { + ValueCallback(data); + EXPECT_EQ(1, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(characteristic->GetValue(), last_read_value_); + + // Obtain writable Heart Rate Control Point characteristic. + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_ + ->GetHeartRateControlPointPath() + .value()); + + characteristic->DeprecatedWriteRemoteCharacteristic( std::vector<uint8_t>({0x01}), base::BindOnce(&BluetoothGattBlueZTest::SuccessCallback, base::Unretained(this)), @@ -1391,6 +1720,62 @@ TEST_F(BluetoothGattBlueZTest, GattCharacteristicValue_Nested_Write_Read) { .value()); characteristic->WriteRemoteCharacteristic( + std::vector<uint8_t>({0x01}), WriteType::kWithResponse, + base::BindLambdaForTesting([&] { + SuccessCallback(); + EXPECT_EQ(1, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + + // Obtain readable Body Sensor Location characteristic. + characteristic = service->GetCharacteristic( + fake_bluetooth_gatt_characteristic_client_ + ->GetBodySensorLocationPath() + .value()); + + characteristic->ReadRemoteCharacteristic( + base::BindOnce(&BluetoothGattBlueZTest::ValueCallback, + base::Unretained(this)), + base::BindOnce(&BluetoothGattBlueZTest::ServiceErrorCallback, + base::Unretained(this))); + }), + base::BindOnce(&BluetoothGattBlueZTest::ServiceErrorCallback, + base::Unretained(this))); + EXPECT_EQ(2, success_callback_count_); + EXPECT_EQ(0, error_callback_count_); + EXPECT_EQ(characteristic->GetValue(), last_read_value_); +} + +// Test a read request issued from the success callback of a write request. +TEST_F(BluetoothGattBlueZTest, + GattCharacteristicValue_Nested_DeprecatedWrite_Read) { + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(bluez::FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(bluez::FakeBluetoothDeviceClient::kLowEnergyPath)); + BluetoothDevice* device = + adapter_->GetDevice(bluez::FakeBluetoothDeviceClient::kLowEnergyAddress); + ASSERT_TRUE(device); + + TestBluetoothAdapterObserver observer(adapter_); + + // Expose the fake Heart Rate service. This will asynchronously expose + // characteristics. + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(bluez::FakeBluetoothDeviceClient::kLowEnergyPath)); + ASSERT_EQ(1, observer.gatt_service_added_count()); + + BluetoothRemoteGattService* service = + device->GetGattService(observer.last_gatt_service_id()); + + // Run the message loop so that the characteristics appear. + base::RunLoop().Run(); + + // Obtain writable Heart Rate Control Point characteristic. + BluetoothRemoteGattCharacteristic* characteristic = + service->GetCharacteristic(fake_bluetooth_gatt_characteristic_client_ + ->GetHeartRateControlPointPath() + .value()); + + characteristic->DeprecatedWriteRemoteCharacteristic( std::vector<uint8_t>({0x01}), base::BindLambdaForTesting([&] { SuccessCallback(); EXPECT_EQ(1, success_callback_count_); @@ -2018,4 +2403,50 @@ TEST_F(BluetoothGattBlueZTest, NotificationType) { } #endif +TEST_F(BluetoothGattBlueZTest, MultipleDevices) { + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(bluez::FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(bluez::FakeBluetoothDeviceClient::kLowEnergyPath)); + bluez::FakeBluetoothDeviceClient::Properties* properties1 = + fake_bluetooth_device_client_->GetProperties( + dbus::ObjectPath(bluez::FakeBluetoothDeviceClient::kLowEnergyPath)); + properties1->services_resolved.ReplaceValue(false); + + fake_bluetooth_gatt_service_client_->ExposeHeartRateService( + dbus::ObjectPath(bluez::FakeBluetoothDeviceClient::kLowEnergyPath)); + while (!fake_bluetooth_gatt_characteristic_client_->IsHeartRateVisible()) + base::RunLoop().RunUntilIdle(); + ASSERT_TRUE(fake_bluetooth_gatt_service_client_->IsHeartRateVisible()); + ASSERT_TRUE(fake_bluetooth_gatt_characteristic_client_->IsHeartRateVisible()); + + fake_bluetooth_device_client_->CreateDevice( + dbus::ObjectPath(bluez::FakeBluetoothAdapterClient::kAdapterPath), + dbus::ObjectPath(bluez::FakeBluetoothDeviceClient::kDualPath)); + bluez::FakeBluetoothDeviceClient::Properties* properties2 = + fake_bluetooth_device_client_->GetProperties( + dbus::ObjectPath(bluez::FakeBluetoothDeviceClient::kDualPath)); + properties2->services_resolved.ReplaceValue(false); + + fake_bluetooth_gatt_service_client_->ExposeBatteryService( + dbus::ObjectPath(bluez::FakeBluetoothDeviceClient::kDualPath)); + ASSERT_TRUE(fake_bluetooth_gatt_service_client_->IsBatteryServiceVisible()); + + BluetoothDeviceBlueZ* device1 = static_cast<BluetoothDeviceBlueZ*>( + adapter_->GetDevice(bluez::FakeBluetoothDeviceClient::kLowEnergyAddress)); + ASSERT_TRUE(device1); + BluetoothDeviceBlueZ* device2 = static_cast<BluetoothDeviceBlueZ*>( + adapter_->GetDevice(bluez::FakeBluetoothDeviceClient::kDualAddress)); + ASSERT_TRUE(device2); + + TestBluetoothAdapterObserver observer(adapter_); + + EXPECT_EQ(0, observer.gatt_discovery_complete_count()); + + properties1->services_resolved.ReplaceValue(true); + properties2->services_resolved.ReplaceValue(true); + + // Since BlueZ iterates all services for all devices for each device, this + // can catch errors like https://crbug.com/1087648 + EXPECT_EQ(2, observer.gatt_discovery_complete_count()); +} } // namespace bluez diff --git a/chromium/device/bluetooth/bluez/bluetooth_pairing_bluez.cc b/chromium/device/bluetooth/bluez/bluetooth_pairing_bluez.cc index b6e96297052..e0bb2b6ae37 100644 --- a/chromium/device/bluetooth/bluez/bluetooth_pairing_bluez.cc +++ b/chromium/device/bluetooth/bluez/bluetooth_pairing_bluez.cc @@ -67,7 +67,7 @@ BluetoothPairingBlueZ::~BluetoothPairingBlueZ() { .Run(bluez::BluetoothAgentServiceProvider::Delegate::CANCELLED); } - pairing_delegate_ = NULL; + pairing_delegate_ = nullptr; } void BluetoothPairingBlueZ::RequestPinCode( diff --git a/chromium/device/bluetooth/bluez/bluetooth_remote_gatt_characteristic_bluez.cc b/chromium/device/bluetooth/bluez/bluetooth_remote_gatt_characteristic_bluez.cc index 18f508f36fb..d68db58794f 100644 --- a/chromium/device/bluetooth/bluez/bluetooth_remote_gatt_characteristic_bluez.cc +++ b/chromium/device/bluetooth/bluez/bluetooth_remote_gatt_characteristic_bluez.cc @@ -24,6 +24,7 @@ #include "device/bluetooth/bluez/bluetooth_remote_gatt_service_bluez.h" #include "device/bluetooth/dbus/bluetooth_gatt_characteristic_client.h" #include "device/bluetooth/dbus/bluez_dbus_manager.h" +#include "third_party/cros_system_api/dbus/bluetooth/dbus-constants.h" #include "third_party/cros_system_api/dbus/service_constants.h" namespace bluez { @@ -173,16 +174,46 @@ void BluetoothRemoteGattCharacteristicBlueZ::ReadRemoteCharacteristic( void BluetoothRemoteGattCharacteristicBlueZ::WriteRemoteCharacteristic( const std::vector<uint8_t>& value, + WriteType write_type, base::OnceClosure callback, ErrorCallback error_callback) { DVLOG(1) << "Sending GATT characteristic write request to characteristic: " << GetIdentifier() << ", UUID: " << GetUUID().canonical_value() + << ", with value: " << value << ", with response: " + << ((write_type == WriteType::kWithoutResponse) ? "no" : "yes") + << "."; + + const char* type_option; + switch (write_type) { + case WriteType::kWithResponse: + type_option = bluetooth_gatt_characteristic::kTypeRequest; + break; + case WriteType::kWithoutResponse: + type_option = bluetooth_gatt_characteristic::kTypeCommand; + break; + } + + bluez::BluezDBusManager::Get() + ->GetBluetoothGattCharacteristicClient() + ->WriteValue( + object_path(), value, type_option, std::move(callback), + base::BindOnce(&BluetoothRemoteGattCharacteristicBlueZ::OnWriteError, + weak_ptr_factory_.GetWeakPtr(), + std::move(error_callback))); +} + +void BluetoothRemoteGattCharacteristicBlueZ:: + DeprecatedWriteRemoteCharacteristic(const std::vector<uint8_t>& value, + base::OnceClosure callback, + ErrorCallback error_callback) { + DVLOG(1) << "Sending GATT characteristic write request to characteristic: " + << GetIdentifier() << ", UUID: " << GetUUID().canonical_value() << ", with value: " << value << "."; bluez::BluezDBusManager::Get() ->GetBluetoothGattCharacteristicClient() ->WriteValue( - object_path(), value, std::move(callback), + object_path(), value, "", std::move(callback), base::BindOnce(&BluetoothRemoteGattCharacteristicBlueZ::OnWriteError, weak_ptr_factory_.GetWeakPtr(), std::move(error_callback))); diff --git a/chromium/device/bluetooth/bluez/bluetooth_remote_gatt_characteristic_bluez.h b/chromium/device/bluetooth/bluez/bluetooth_remote_gatt_characteristic_bluez.h index f435e4ddbd4..164aa707956 100644 --- a/chromium/device/bluetooth/bluez/bluetooth_remote_gatt_characteristic_bluez.h +++ b/chromium/device/bluetooth/bluez/bluetooth_remote_gatt_characteristic_bluez.h @@ -57,8 +57,13 @@ class BluetoothRemoteGattCharacteristicBlueZ void ReadRemoteCharacteristic(ValueCallback callback, ErrorCallback error_callback) override; void WriteRemoteCharacteristic(const std::vector<uint8_t>& value, + WriteType write_type, base::OnceClosure callback, ErrorCallback error_callback) override; + void DeprecatedWriteRemoteCharacteristic( + const std::vector<uint8_t>& value, + base::OnceClosure callback, + ErrorCallback error_callback) override; #if defined(OS_CHROMEOS) void PrepareWriteRemoteCharacteristic(const std::vector<uint8_t>& value, base::OnceClosure callback, diff --git a/chromium/device/bluetooth/cast/bluetooth_adapter_cast.cc b/chromium/device/bluetooth/cast/bluetooth_adapter_cast.cc index 1eee419655c..5113465e533 100644 --- a/chromium/device/bluetooth/cast/bluetooth_adapter_cast.cc +++ b/chromium/device/bluetooth/cast/bluetooth_adapter_cast.cc @@ -9,6 +9,7 @@ #include "base/bind.h" #include "base/callback_helpers.h" #include "base/location.h" +#include "base/logging.h" #include "base/no_destructor.h" #include "base/task/post_task.h" #include "base/threading/sequenced_task_runner_handle.h" diff --git a/chromium/device/bluetooth/cast/bluetooth_device_cast.cc b/chromium/device/bluetooth/cast/bluetooth_device_cast.cc index 4ec0b249e26..17da5222f60 100644 --- a/chromium/device/bluetooth/cast/bluetooth_device_cast.cc +++ b/chromium/device/bluetooth/cast/bluetooth_device_cast.cc @@ -10,6 +10,7 @@ #include <utility> #include "base/bind.h" +#include "base/logging.h" #include "base/strings/stringprintf.h" #include "chromecast/device/bluetooth/bluetooth_util.h" #include "chromecast/device/bluetooth/le/remote_characteristic.h" diff --git a/chromium/device/bluetooth/cast/bluetooth_remote_gatt_characteristic_cast.cc b/chromium/device/bluetooth/cast/bluetooth_remote_gatt_characteristic_cast.cc index 83cdc280981..e52f00359e6 100644 --- a/chromium/device/bluetooth/cast/bluetooth_remote_gatt_characteristic_cast.cc +++ b/chromium/device/bluetooth/cast/bluetooth_remote_gatt_characteristic_cast.cc @@ -10,10 +10,12 @@ #include "base/callback.h" #include "base/callback_forward.h" #include "base/containers/queue.h" +#include "base/logging.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "chromecast/device/bluetooth/le/remote_characteristic.h" #include "chromecast/device/bluetooth/le/remote_descriptor.h" +#include "chromecast/public/bluetooth/gatt.h" #include "device/bluetooth/cast/bluetooth_remote_gatt_descriptor_cast.h" #include "device/bluetooth/cast/bluetooth_remote_gatt_service_cast.h" #include "device/bluetooth/cast/bluetooth_utils.h" @@ -143,6 +145,32 @@ void BluetoothRemoteGattCharacteristicCast::ReadRemoteCharacteristic( void BluetoothRemoteGattCharacteristicCast::WriteRemoteCharacteristic( const std::vector<uint8_t>& value, + WriteType write_type, + base::OnceClosure callback, + ErrorCallback error_callback) { + using ChromecastWriteType = chromecast::bluetooth_v2_shlib::Gatt::WriteType; + + ChromecastWriteType chromecast_write_type; + switch (write_type) { + case WriteType::kWithResponse: + chromecast_write_type = ChromecastWriteType::WRITE_TYPE_DEFAULT; + break; + case WriteType::kWithoutResponse: + chromecast_write_type = ChromecastWriteType::WRITE_TYPE_NO_RESPONSE; + break; + } + + remote_characteristic_->WriteAuth( + chromecast::bluetooth_v2_shlib::Gatt::Client::AUTH_REQ_NONE, + chromecast_write_type, value, + base::BindOnce( + &BluetoothRemoteGattCharacteristicCast::OnWriteRemoteCharacteristic, + weak_factory_.GetWeakPtr(), value, std::move(callback), + std::move(error_callback))); +} + +void BluetoothRemoteGattCharacteristicCast::DeprecatedWriteRemoteCharacteristic( + const std::vector<uint8_t>& value, base::OnceClosure callback, ErrorCallback error_callback) { remote_characteristic_->Write( diff --git a/chromium/device/bluetooth/cast/bluetooth_remote_gatt_characteristic_cast.h b/chromium/device/bluetooth/cast/bluetooth_remote_gatt_characteristic_cast.h index f42c545fece..af4dd712c5f 100644 --- a/chromium/device/bluetooth/cast/bluetooth_remote_gatt_characteristic_cast.h +++ b/chromium/device/bluetooth/cast/bluetooth_remote_gatt_characteristic_cast.h @@ -48,8 +48,13 @@ class BluetoothRemoteGattCharacteristicCast void ReadRemoteCharacteristic(ValueCallback callback, ErrorCallback error_callback) override; void WriteRemoteCharacteristic(const std::vector<uint8_t>& value, + WriteType write_type, base::OnceClosure callback, ErrorCallback error_callback) override; + void DeprecatedWriteRemoteCharacteristic( + const std::vector<uint8_t>& value, + base::OnceClosure callback, + ErrorCallback error_callback) override; // Called by BluetoothAdapterCast to set the value when a new notification // comes in. diff --git a/chromium/device/bluetooth/dbus/bluetooth_gatt_application_service_provider.cc b/chromium/device/bluetooth/dbus/bluetooth_gatt_application_service_provider.cc index 6ccfcc27914..611e810ffb6 100644 --- a/chromium/device/bluetooth/dbus/bluetooth_gatt_application_service_provider.cc +++ b/chromium/device/bluetooth/dbus/bluetooth_gatt_application_service_provider.cc @@ -7,6 +7,7 @@ #include <string> #include <utility> +#include "base/logging.h" #include "base/memory/ptr_util.h" #include "device/bluetooth/bluetooth_gatt_characteristic.h" #include "device/bluetooth/bluez/bluetooth_gatt_service_bluez.h" diff --git a/chromium/device/bluetooth/dbus/bluetooth_gatt_characteristic_client.cc b/chromium/device/bluetooth/dbus/bluetooth_gatt_characteristic_client.cc index ec4a928cdb2..4ae1703db72 100644 --- a/chromium/device/bluetooth/dbus/bluetooth_gatt_characteristic_client.cc +++ b/chromium/device/bluetooth/dbus/bluetooth_gatt_characteristic_client.cc @@ -7,6 +7,7 @@ #include <stddef.h> #include "base/bind.h" +#include "base/logging.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "base/observer_list.h" @@ -14,6 +15,7 @@ #include "dbus/bus.h" #include "dbus/object_manager.h" #include "dbus/values_util.h" +#include "third_party/cros_system_api/dbus/bluetooth/dbus-constants.h" #include "third_party/cros_system_api/dbus/service_constants.h" namespace bluez { @@ -113,6 +115,7 @@ class BluetoothGattCharacteristicClientImpl // BluetoothGattCharacteristicClient override. void WriteValue(const dbus::ObjectPath& object_path, const std::vector<uint8_t>& value, + base::StringPiece type_option, base::OnceClosure callback, ErrorCallback error_callback) override { dbus::ObjectProxy* object_proxy = @@ -128,8 +131,14 @@ class BluetoothGattCharacteristicClientImpl dbus::MessageWriter writer(&method_call); writer.AppendArrayOfBytes(value.data(), value.size()); - // Append empty option dict + // Append option dict base::DictionaryValue dict; + if (!type_option.empty()) { + // NB: the "type" option was added in BlueZ 5.51. Older versions of BlueZ + // will ignore this option. + dict.SetStringKey(bluetooth_gatt_characteristic::kOptionType, + type_option); + } dbus::AppendValueData(&writer, dict); object_proxy->CallMethodWithErrorCallback( diff --git a/chromium/device/bluetooth/dbus/bluetooth_gatt_characteristic_client.h b/chromium/device/bluetooth/dbus/bluetooth_gatt_characteristic_client.h index b76a8317d39..93b4cc76c15 100644 --- a/chromium/device/bluetooth/dbus/bluetooth_gatt_characteristic_client.h +++ b/chromium/device/bluetooth/dbus/bluetooth_gatt_characteristic_client.h @@ -105,10 +105,12 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothGattCharacteristicClient ErrorCallback error_callback) = 0; // Issues a request to write the value of GATT characteristic with object path - // |object_path| with value |value|. Invokes |callback| on success and - // |error_callback| on failure. + // |object_path| with value |value| and |type_option|. |type_option| is + // bluetooth_gatt_characteristic::kTypeRequest or kTypeCommand, or "" to omit + // the option. Invokes |callback| on success and |error_callback| on failure. virtual void WriteValue(const dbus::ObjectPath& object_path, const std::vector<uint8_t>& value, + base::StringPiece type_option, base::OnceClosure callback, ErrorCallback error_callback) = 0; diff --git a/chromium/device/bluetooth/dbus/bluetooth_gatt_descriptor_client.cc b/chromium/device/bluetooth/dbus/bluetooth_gatt_descriptor_client.cc index ff3f0d53765..c2fbd87c6fd 100644 --- a/chromium/device/bluetooth/dbus/bluetooth_gatt_descriptor_client.cc +++ b/chromium/device/bluetooth/dbus/bluetooth_gatt_descriptor_client.cc @@ -7,6 +7,7 @@ #include <stddef.h> #include "base/bind.h" +#include "base/logging.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "base/observer_list.h" diff --git a/chromium/device/bluetooth/dbus/bluetooth_gatt_service_client.cc b/chromium/device/bluetooth/dbus/bluetooth_gatt_service_client.cc index 014f26f6ceb..2ea601b5c78 100644 --- a/chromium/device/bluetooth/dbus/bluetooth_gatt_service_client.cc +++ b/chromium/device/bluetooth/dbus/bluetooth_gatt_service_client.cc @@ -5,6 +5,7 @@ #include "device/bluetooth/dbus/bluetooth_gatt_service_client.h" #include "base/bind.h" +#include "base/logging.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "base/observer_list.h" diff --git a/chromium/device/bluetooth/dbus/bluez_dbus_manager.cc b/chromium/device/bluetooth/dbus/bluez_dbus_manager.cc index 9edc0a39a1a..bd1df9bfe7b 100644 --- a/chromium/device/bluetooth/dbus/bluez_dbus_manager.cc +++ b/chromium/device/bluetooth/dbus/bluez_dbus_manager.cc @@ -10,6 +10,7 @@ #include "base/bind.h" #include "base/command_line.h" #include "base/feature_list.h" +#include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/system/sys_info.h" #include "base/threading/thread.h" diff --git a/chromium/device/bluetooth/dbus/bluez_dbus_thread_manager.cc b/chromium/device/bluetooth/dbus/bluez_dbus_thread_manager.cc index 31793710e2b..8b606656703 100644 --- a/chromium/device/bluetooth/dbus/bluez_dbus_thread_manager.cc +++ b/chromium/device/bluetooth/dbus/bluez_dbus_thread_manager.cc @@ -4,6 +4,7 @@ #include "device/bluetooth/dbus/bluez_dbus_thread_manager.h" +#include "base/logging.h" #include "base/message_loop/message_pump_type.h" #include "base/threading/thread.h" #include "dbus/bus.h" diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_agent_manager_client.cc b/chromium/device/bluetooth/dbus/fake_bluetooth_agent_manager_client.cc index 4885352ee59..48107e1091d 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_agent_manager_client.cc +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_agent_manager_client.cc @@ -11,7 +11,7 @@ namespace bluez { FakeBluetoothAgentManagerClient::FakeBluetoothAgentManagerClient() - : service_provider_(NULL) {} + : service_provider_(nullptr) {} FakeBluetoothAgentManagerClient::~FakeBluetoothAgentManagerClient() = default; @@ -81,7 +81,7 @@ void FakeBluetoothAgentManagerClient::RegisterAgentServiceProvider( void FakeBluetoothAgentManagerClient::UnregisterAgentServiceProvider( FakeBluetoothAgentServiceProvider* service_provider) { if (service_provider_ == service_provider) - service_provider_ = NULL; + service_provider_ = nullptr; } FakeBluetoothAgentServiceProvider* diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_agent_service_provider.cc b/chromium/device/bluetooth/dbus/fake_bluetooth_agent_service_provider.cc index 2266c059d4f..edfe29d3b31 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_agent_service_provider.cc +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_agent_service_provider.cc @@ -4,6 +4,7 @@ #include "device/bluetooth/dbus/fake_bluetooth_agent_service_provider.h" +#include "base/logging.h" #include "device/bluetooth/dbus/bluez_dbus_manager.h" #include "device/bluetooth/dbus/fake_bluetooth_agent_manager_client.h" diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_device_client.cc b/chromium/device/bluetooth/dbus/fake_bluetooth_device_client.cc index 3f137c01978..23c5fe33b0b 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_device_client.cc +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_device_client.cc @@ -650,6 +650,7 @@ void FakeBluetoothDeviceClient::ExecuteWrite( bluez::BluezDBusManager::Get() ->GetBluetoothGattCharacteristicClient() ->WriteValue(prepare_write_request.first, prepare_write_request.second, + bluetooth_gatt_characteristic::kTypeRequest, base::DoNothing(), base::DoNothing()); } prepare_write_requests_.clear(); @@ -882,248 +883,242 @@ void FakeBluetoothDeviceClient::CreateDeviceWithProperties( observer.DeviceAdded(device_path); } -std::unique_ptr<base::ListValue> -FakeBluetoothDeviceClient::GetBluetoothDevicesAsDictionaries() const { - std::unique_ptr<base::ListValue> predefined_devices(new base::ListValue); - std::unique_ptr<base::DictionaryValue> pairedDevice( - new base::DictionaryValue); - pairedDevice->SetString("path", kPairedDevicePath); - pairedDevice->SetString("address", kPairedDeviceAddress); - pairedDevice->SetString("name", kPairedDeviceName); - pairedDevice->SetString("alias", kPairedDeviceName); - pairedDevice->SetString("pairingMethod", ""); - pairedDevice->SetString("pairingAuthToken", ""); - pairedDevice->SetString("pairingAction", ""); - pairedDevice->SetInteger("classValue", kPairedDeviceClass); - pairedDevice->SetBoolean("discoverable", true); - pairedDevice->SetBoolean("isTrusted", true); - pairedDevice->SetBoolean("paired", true); - pairedDevice->SetBoolean("incoming", false); - predefined_devices->Append(std::move(pairedDevice)); - - std::unique_ptr<base::DictionaryValue> legacyDevice( - new base::DictionaryValue); - legacyDevice->SetString("path", kLegacyAutopairPath); - legacyDevice->SetString("address", kLegacyAutopairAddress); - legacyDevice->SetString("name", kLegacyAutopairName); - legacyDevice->SetString("alias", kLegacyAutopairName); - legacyDevice->SetString("pairingMethod", ""); - legacyDevice->SetString("pairingAuthToken", ""); - legacyDevice->SetString("pairingAction", ""); - legacyDevice->SetInteger("classValue", kLegacyAutopairClass); - legacyDevice->SetBoolean("isTrusted", true); - legacyDevice->SetBoolean("discoverable", false); - legacyDevice->SetBoolean("paired", false); - legacyDevice->SetBoolean("incoming", false); - predefined_devices->Append(std::move(legacyDevice)); - - std::unique_ptr<base::DictionaryValue> pin(new base::DictionaryValue); - pin->SetString("path", kDisplayPinCodePath); - pin->SetString("address", kDisplayPinCodeAddress); - pin->SetString("name", kDisplayPinCodeName); - pin->SetString("alias", kDisplayPinCodeName); - pin->SetString("pairingMethod", kPairingMethodPinCode); - pin->SetString("pairingAuthToken", kTestPinCode); - pin->SetString("pairingAction", kPairingActionDisplay); - pin->SetInteger("classValue", kDisplayPinCodeClass); - pin->SetBoolean("isTrusted", false); - pin->SetBoolean("discoverable", false); - pin->SetBoolean("paired", false); - pin->SetBoolean("incoming", false); - predefined_devices->Append(std::move(pin)); - - std::unique_ptr<base::DictionaryValue> vanishing(new base::DictionaryValue); - vanishing->SetString("path", kVanishingDevicePath); - vanishing->SetString("address", kVanishingDeviceAddress); - vanishing->SetString("name", kVanishingDeviceName); - vanishing->SetString("alias", kVanishingDeviceName); - vanishing->SetString("pairingMethod", ""); - vanishing->SetString("pairingAuthToken", ""); - vanishing->SetString("pairingAction", ""); - vanishing->SetInteger("classValue", kVanishingDeviceClass); - vanishing->SetBoolean("isTrusted", false); - vanishing->SetBoolean("discoverable", false); - vanishing->SetBoolean("paired", false); - vanishing->SetBoolean("incoming", false); - predefined_devices->Append(std::move(vanishing)); - - std::unique_ptr<base::DictionaryValue> connect_unpairable( - new base::DictionaryValue); - connect_unpairable->SetString("path", kConnectUnpairablePath); - connect_unpairable->SetString("address", kConnectUnpairableAddress); - connect_unpairable->SetString("name", kConnectUnpairableName); - connect_unpairable->SetString("pairingMethod", ""); - connect_unpairable->SetString("pairingAuthToken", ""); - connect_unpairable->SetString("pairingAction", ""); - connect_unpairable->SetString("alias", kConnectUnpairableName); - connect_unpairable->SetInteger("classValue", kConnectUnpairableClass); - connect_unpairable->SetBoolean("isTrusted", false); - connect_unpairable->SetBoolean("discoverable", false); - connect_unpairable->SetBoolean("paired", false); - connect_unpairable->SetBoolean("incoming", false); - predefined_devices->Append(std::move(connect_unpairable)); - - std::unique_ptr<base::DictionaryValue> passkey(new base::DictionaryValue); - passkey->SetString("path", kDisplayPasskeyPath); - passkey->SetString("address", kDisplayPasskeyAddress); - passkey->SetString("name", kDisplayPasskeyName); - passkey->SetString("alias", kDisplayPasskeyName); - passkey->SetString("pairingMethod", kPairingMethodPassKey); - passkey->SetInteger("pairingAuthToken", kTestPassKey); - passkey->SetString("pairingAction", kPairingActionDisplay); - passkey->SetInteger("classValue", kDisplayPasskeyClass); - passkey->SetBoolean("isTrusted", false); - passkey->SetBoolean("discoverable", false); - passkey->SetBoolean("paired", false); - passkey->SetBoolean("incoming", false); - predefined_devices->Append(std::move(passkey)); - - std::unique_ptr<base::DictionaryValue> request_pin(new base::DictionaryValue); - request_pin->SetString("path", kRequestPinCodePath); - request_pin->SetString("address", kRequestPinCodeAddress); - request_pin->SetString("name", kRequestPinCodeName); - request_pin->SetString("alias", kRequestPinCodeName); - request_pin->SetString("pairingMethod", ""); - request_pin->SetString("pairingAuthToken", ""); - request_pin->SetString("pairingAction", kPairingActionRequest); - request_pin->SetInteger("classValue", kRequestPinCodeClass); - request_pin->SetBoolean("isTrusted", false); - request_pin->SetBoolean("discoverable", false); - request_pin->SetBoolean("paired", false); - request_pin->SetBoolean("incoming", false); - predefined_devices->Append(std::move(request_pin)); - - std::unique_ptr<base::DictionaryValue> confirm(new base::DictionaryValue); - confirm->SetString("path", kConfirmPasskeyPath); - confirm->SetString("address", kConfirmPasskeyAddress); - confirm->SetString("name", kConfirmPasskeyName); - confirm->SetString("alias", kConfirmPasskeyName); - confirm->SetString("pairingMethod", ""); - confirm->SetInteger("pairingAuthToken", kTestPassKey); - confirm->SetString("pairingAction", kPairingActionConfirmation); - confirm->SetInteger("classValue", kConfirmPasskeyClass); - confirm->SetBoolean("isTrusted", false); - confirm->SetBoolean("discoverable", false); - confirm->SetBoolean("paired", false); - confirm->SetBoolean("incoming", false); - predefined_devices->Append(std::move(confirm)); - - std::unique_ptr<base::DictionaryValue> request_passkey( - new base::DictionaryValue); - request_passkey->SetString("path", kRequestPasskeyPath); - request_passkey->SetString("address", kRequestPasskeyAddress); - request_passkey->SetString("name", kRequestPasskeyName); - request_passkey->SetString("alias", kRequestPasskeyName); - request_passkey->SetString("pairingMethod", kPairingMethodPassKey); - request_passkey->SetString("pairingAction", kPairingActionRequest); - request_passkey->SetInteger("pairingAuthToken", kTestPassKey); - request_passkey->SetInteger("classValue", kRequestPasskeyClass); - request_passkey->SetBoolean("isTrusted", false); - request_passkey->SetBoolean("discoverable", false); - request_passkey->SetBoolean("paired", false); - request_passkey->SetBoolean("incoming", false); - predefined_devices->Append(std::move(request_passkey)); - - std::unique_ptr<base::DictionaryValue> unconnectable( - new base::DictionaryValue); - unconnectable->SetString("path", kUnconnectableDevicePath); - unconnectable->SetString("address", kUnconnectableDeviceAddress); - unconnectable->SetString("name", kUnconnectableDeviceName); - unconnectable->SetString("alias", kUnconnectableDeviceName); - unconnectable->SetString("pairingMethod", ""); - unconnectable->SetString("pairingAuthToken", ""); - unconnectable->SetString("pairingAction", ""); - unconnectable->SetInteger("classValue", kUnconnectableDeviceClass); - unconnectable->SetBoolean("isTrusted", true); - unconnectable->SetBoolean("discoverable", false); - unconnectable->SetBoolean("paired", false); - unconnectable->SetBoolean("incoming", false); - predefined_devices->Append(std::move(unconnectable)); - - std::unique_ptr<base::DictionaryValue> unpairable(new base::DictionaryValue); - unpairable->SetString("path", kUnpairableDevicePath); - unpairable->SetString("address", kUnpairableDeviceAddress); - unpairable->SetString("name", kUnpairableDeviceName); - unpairable->SetString("alias", kUnpairableDeviceName); - unpairable->SetString("pairingMethod", ""); - unpairable->SetString("pairingAuthToken", ""); - unpairable->SetString("pairingAction", kPairingActionFail); - unpairable->SetInteger("classValue", kUnpairableDeviceClass); - unpairable->SetBoolean("isTrusted", false); - unpairable->SetBoolean("discoverable", false); - unpairable->SetBoolean("paired", false); - unpairable->SetBoolean("incoming", false); - predefined_devices->Append(std::move(unpairable)); - - std::unique_ptr<base::DictionaryValue> just_works(new base::DictionaryValue); - just_works->SetString("path", kJustWorksPath); - just_works->SetString("address", kJustWorksAddress); - just_works->SetString("name", kJustWorksName); - just_works->SetString("alias", kJustWorksName); - just_works->SetString("pairingMethod", ""); - just_works->SetString("pairingAuthToken", ""); - just_works->SetString("pairingAction", ""); - just_works->SetInteger("classValue", kJustWorksClass); - just_works->SetBoolean("isTrusted", false); - just_works->SetBoolean("discoverable", false); - just_works->SetBoolean("paired", false); - just_works->SetBoolean("incoming", false); - predefined_devices->Append(std::move(just_works)); - - std::unique_ptr<base::DictionaryValue> low_energy(new base::DictionaryValue); - low_energy->SetString("path", kLowEnergyPath); - low_energy->SetString("address", kLowEnergyAddress); - low_energy->SetString("name", kLowEnergyName); - low_energy->SetString("alias", kLowEnergyName); - low_energy->SetString("pairingMethod", ""); - low_energy->SetString("pairingAuthToken", ""); - low_energy->SetString("pairingAction", ""); - low_energy->SetInteger("classValue", kLowEnergyClass); - low_energy->SetBoolean("isTrusted", false); - low_energy->SetBoolean("discoverable", false); - low_energy->SetBoolean("paireed", false); - low_energy->SetBoolean("incoming", false); - predefined_devices->Append(std::move(low_energy)); - - std::unique_ptr<base::DictionaryValue> paired_unconnectable( - new base::DictionaryValue); - paired_unconnectable->SetString("path", kPairedUnconnectableDevicePath); - paired_unconnectable->SetString("address", kPairedUnconnectableDeviceAddress); - paired_unconnectable->SetString("name", kPairedUnconnectableDeviceName); - paired_unconnectable->SetString("pairingMethod", ""); - paired_unconnectable->SetString("pairingAuthToken", ""); - paired_unconnectable->SetString("pairingAction", ""); - paired_unconnectable->SetString("alias", kPairedUnconnectableDeviceName); - paired_unconnectable->SetInteger("classValue", - kPairedUnconnectableDeviceClass); - paired_unconnectable->SetBoolean("isTrusted", false); - paired_unconnectable->SetBoolean("discoverable", true); - paired_unconnectable->SetBoolean("paired", true); - paired_unconnectable->SetBoolean("incoming", false); - predefined_devices->Append(std::move(paired_unconnectable)); - - std::unique_ptr<base::DictionaryValue> connected_trusted_not_paired( - new base::DictionaryValue); - connected_trusted_not_paired->SetString("path", - kConnectedTrustedNotPairedDevicePath); - connected_trusted_not_paired->SetString( +base::Value FakeBluetoothDeviceClient::GetBluetoothDevicesAsDictionaries() + const { + base::Value::ListStorage predefined_devices; + + base::Value paired_device(base::Value::Type::DICTIONARY); + paired_device.SetStringKey("path", kPairedDevicePath); + paired_device.SetStringKey("address", kPairedDeviceAddress); + paired_device.SetStringKey("name", kPairedDeviceName); + paired_device.SetStringKey("alias", kPairedDeviceName); + paired_device.SetStringKey("pairingMethod", ""); + paired_device.SetStringKey("pairingAuthToken", ""); + paired_device.SetStringKey("pairingAction", ""); + paired_device.SetIntKey("classValue", kPairedDeviceClass); + paired_device.SetBoolKey("discoverable", true); + paired_device.SetBoolKey("isTrusted", true); + paired_device.SetBoolKey("paired", true); + paired_device.SetBoolKey("incoming", false); + predefined_devices.push_back(std::move(paired_device)); + + base::Value legacy_device(base::Value::Type::DICTIONARY); + legacy_device.SetStringKey("path", kLegacyAutopairPath); + legacy_device.SetStringKey("address", kLegacyAutopairAddress); + legacy_device.SetStringKey("name", kLegacyAutopairName); + legacy_device.SetStringKey("alias", kLegacyAutopairName); + legacy_device.SetStringKey("pairingMethod", ""); + legacy_device.SetStringKey("pairingAuthToken", ""); + legacy_device.SetStringKey("pairingAction", ""); + legacy_device.SetIntKey("classValue", kLegacyAutopairClass); + legacy_device.SetBoolKey("isTrusted", true); + legacy_device.SetBoolKey("discoverable", false); + legacy_device.SetBoolKey("paired", false); + legacy_device.SetBoolKey("incoming", false); + predefined_devices.push_back(std::move(legacy_device)); + + base::Value pin(base::Value::Type::DICTIONARY); + pin.SetStringKey("path", kDisplayPinCodePath); + pin.SetStringKey("address", kDisplayPinCodeAddress); + pin.SetStringKey("name", kDisplayPinCodeName); + pin.SetStringKey("alias", kDisplayPinCodeName); + pin.SetStringKey("pairingMethod", kPairingMethodPinCode); + pin.SetStringKey("pairingAuthToken", kTestPinCode); + pin.SetStringKey("pairingAction", kPairingActionDisplay); + pin.SetIntKey("classValue", kDisplayPinCodeClass); + pin.SetBoolKey("isTrusted", false); + pin.SetBoolKey("discoverable", false); + pin.SetBoolKey("paired", false); + pin.SetBoolKey("incoming", false); + predefined_devices.push_back(std::move(pin)); + + base::Value vanishing(base::Value::Type::DICTIONARY); + vanishing.SetStringKey("path", kVanishingDevicePath); + vanishing.SetStringKey("address", kVanishingDeviceAddress); + vanishing.SetStringKey("name", kVanishingDeviceName); + vanishing.SetStringKey("alias", kVanishingDeviceName); + vanishing.SetStringKey("pairingMethod", ""); + vanishing.SetStringKey("pairingAuthToken", ""); + vanishing.SetStringKey("pairingAction", ""); + vanishing.SetIntKey("classValue", kVanishingDeviceClass); + vanishing.SetBoolKey("isTrusted", false); + vanishing.SetBoolKey("discoverable", false); + vanishing.SetBoolKey("paired", false); + vanishing.SetBoolKey("incoming", false); + predefined_devices.push_back(std::move(vanishing)); + + base::Value connect_unpairable(base::Value::Type::DICTIONARY); + connect_unpairable.SetStringKey("path", kConnectUnpairablePath); + connect_unpairable.SetStringKey("address", kConnectUnpairableAddress); + connect_unpairable.SetStringKey("name", kConnectUnpairableName); + connect_unpairable.SetStringKey("pairingMethod", ""); + connect_unpairable.SetStringKey("pairingAuthToken", ""); + connect_unpairable.SetStringKey("pairingAction", ""); + connect_unpairable.SetStringKey("alias", kConnectUnpairableName); + connect_unpairable.SetIntKey("classValue", kConnectUnpairableClass); + connect_unpairable.SetBoolKey("isTrusted", false); + connect_unpairable.SetBoolKey("discoverable", false); + connect_unpairable.SetBoolKey("paired", false); + connect_unpairable.SetBoolKey("incoming", false); + predefined_devices.push_back(std::move(connect_unpairable)); + + base::Value passkey(base::Value::Type::DICTIONARY); + passkey.SetStringKey("path", kDisplayPasskeyPath); + passkey.SetStringKey("address", kDisplayPasskeyAddress); + passkey.SetStringKey("name", kDisplayPasskeyName); + passkey.SetStringKey("alias", kDisplayPasskeyName); + passkey.SetStringKey("pairingMethod", kPairingMethodPassKey); + passkey.SetIntKey("pairingAuthToken", kTestPassKey); + passkey.SetStringKey("pairingAction", kPairingActionDisplay); + passkey.SetIntKey("classValue", kDisplayPasskeyClass); + passkey.SetBoolKey("isTrusted", false); + passkey.SetBoolKey("discoverable", false); + passkey.SetBoolKey("paired", false); + passkey.SetBoolKey("incoming", false); + predefined_devices.push_back(std::move(passkey)); + + base::Value request_pin(base::Value::Type::DICTIONARY); + request_pin.SetStringKey("path", kRequestPinCodePath); + request_pin.SetStringKey("address", kRequestPinCodeAddress); + request_pin.SetStringKey("name", kRequestPinCodeName); + request_pin.SetStringKey("alias", kRequestPinCodeName); + request_pin.SetStringKey("pairingMethod", ""); + request_pin.SetStringKey("pairingAuthToken", ""); + request_pin.SetStringKey("pairingAction", kPairingActionRequest); + request_pin.SetIntKey("classValue", kRequestPinCodeClass); + request_pin.SetBoolKey("isTrusted", false); + request_pin.SetBoolKey("discoverable", false); + request_pin.SetBoolKey("paired", false); + request_pin.SetBoolKey("incoming", false); + predefined_devices.push_back(std::move(request_pin)); + + base::Value confirm(base::Value::Type::DICTIONARY); + confirm.SetStringKey("path", kConfirmPasskeyPath); + confirm.SetStringKey("address", kConfirmPasskeyAddress); + confirm.SetStringKey("name", kConfirmPasskeyName); + confirm.SetStringKey("alias", kConfirmPasskeyName); + confirm.SetStringKey("pairingMethod", ""); + confirm.SetIntKey("pairingAuthToken", kTestPassKey); + confirm.SetStringKey("pairingAction", kPairingActionConfirmation); + confirm.SetIntKey("classValue", kConfirmPasskeyClass); + confirm.SetBoolKey("isTrusted", false); + confirm.SetBoolKey("discoverable", false); + confirm.SetBoolKey("paired", false); + confirm.SetBoolKey("incoming", false); + predefined_devices.push_back(std::move(confirm)); + + base::Value request_passkey(base::Value::Type::DICTIONARY); + request_passkey.SetStringKey("path", kRequestPasskeyPath); + request_passkey.SetStringKey("address", kRequestPasskeyAddress); + request_passkey.SetStringKey("name", kRequestPasskeyName); + request_passkey.SetStringKey("alias", kRequestPasskeyName); + request_passkey.SetStringKey("pairingMethod", kPairingMethodPassKey); + request_passkey.SetStringKey("pairingAction", kPairingActionRequest); + request_passkey.SetIntKey("pairingAuthToken", kTestPassKey); + request_passkey.SetIntKey("classValue", kRequestPasskeyClass); + request_passkey.SetBoolKey("isTrusted", false); + request_passkey.SetBoolKey("discoverable", false); + request_passkey.SetBoolKey("paired", false); + request_passkey.SetBoolKey("incoming", false); + predefined_devices.push_back(std::move(request_passkey)); + + base::Value unconnectable(base::Value::Type::DICTIONARY); + unconnectable.SetStringKey("path", kUnconnectableDevicePath); + unconnectable.SetStringKey("address", kUnconnectableDeviceAddress); + unconnectable.SetStringKey("name", kUnconnectableDeviceName); + unconnectable.SetStringKey("alias", kUnconnectableDeviceName); + unconnectable.SetStringKey("pairingMethod", ""); + unconnectable.SetStringKey("pairingAuthToken", ""); + unconnectable.SetStringKey("pairingAction", ""); + unconnectable.SetIntKey("classValue", kUnconnectableDeviceClass); + unconnectable.SetBoolKey("isTrusted", true); + unconnectable.SetBoolKey("discoverable", false); + unconnectable.SetBoolKey("paired", false); + unconnectable.SetBoolKey("incoming", false); + predefined_devices.push_back(std::move(unconnectable)); + + base::Value unpairable(base::Value::Type::DICTIONARY); + unpairable.SetStringKey("path", kUnpairableDevicePath); + unpairable.SetStringKey("address", kUnpairableDeviceAddress); + unpairable.SetStringKey("name", kUnpairableDeviceName); + unpairable.SetStringKey("alias", kUnpairableDeviceName); + unpairable.SetStringKey("pairingMethod", ""); + unpairable.SetStringKey("pairingAuthToken", ""); + unpairable.SetStringKey("pairingAction", kPairingActionFail); + unpairable.SetIntKey("classValue", kUnpairableDeviceClass); + unpairable.SetBoolKey("isTrusted", false); + unpairable.SetBoolKey("discoverable", false); + unpairable.SetBoolKey("paired", false); + unpairable.SetBoolKey("incoming", false); + predefined_devices.push_back(std::move(unpairable)); + + base::Value just_works(base::Value::Type::DICTIONARY); + just_works.SetStringKey("path", kJustWorksPath); + just_works.SetStringKey("address", kJustWorksAddress); + just_works.SetStringKey("name", kJustWorksName); + just_works.SetStringKey("alias", kJustWorksName); + just_works.SetStringKey("pairingMethod", ""); + just_works.SetStringKey("pairingAuthToken", ""); + just_works.SetStringKey("pairingAction", ""); + just_works.SetIntKey("classValue", kJustWorksClass); + just_works.SetBoolKey("isTrusted", false); + just_works.SetBoolKey("discoverable", false); + just_works.SetBoolKey("paired", false); + just_works.SetBoolKey("incoming", false); + predefined_devices.push_back(std::move(just_works)); + + base::Value low_energy(base::Value::Type::DICTIONARY); + low_energy.SetStringKey("path", kLowEnergyPath); + low_energy.SetStringKey("address", kLowEnergyAddress); + low_energy.SetStringKey("name", kLowEnergyName); + low_energy.SetStringKey("alias", kLowEnergyName); + low_energy.SetStringKey("pairingMethod", ""); + low_energy.SetStringKey("pairingAuthToken", ""); + low_energy.SetStringKey("pairingAction", ""); + low_energy.SetIntKey("classValue", kLowEnergyClass); + low_energy.SetBoolKey("isTrusted", false); + low_energy.SetBoolKey("discoverable", false); + low_energy.SetBoolKey("paireed", false); + low_energy.SetBoolKey("incoming", false); + predefined_devices.push_back(std::move(low_energy)); + + base::Value paired_unconnectable(base::Value::Type::DICTIONARY); + paired_unconnectable.SetStringKey("path", kPairedUnconnectableDevicePath); + paired_unconnectable.SetStringKey("address", + kPairedUnconnectableDeviceAddress); + paired_unconnectable.SetStringKey("name", kPairedUnconnectableDeviceName); + paired_unconnectable.SetStringKey("pairingMethod", ""); + paired_unconnectable.SetStringKey("pairingAuthToken", ""); + paired_unconnectable.SetStringKey("pairingAction", ""); + paired_unconnectable.SetStringKey("alias", kPairedUnconnectableDeviceName); + paired_unconnectable.SetIntKey("classValue", kPairedUnconnectableDeviceClass); + paired_unconnectable.SetBoolKey("isTrusted", false); + paired_unconnectable.SetBoolKey("discoverable", true); + paired_unconnectable.SetBoolKey("paired", true); + paired_unconnectable.SetBoolKey("incoming", false); + predefined_devices.push_back(std::move(paired_unconnectable)); + + base::Value connected_trusted_not_paired(base::Value::Type::DICTIONARY); + connected_trusted_not_paired.SetStringKey( + "path", kConnectedTrustedNotPairedDevicePath); + connected_trusted_not_paired.SetStringKey( "address", kConnectedTrustedNotPairedDeviceAddress); - connected_trusted_not_paired->SetString("name", - kConnectedTrustedNotPairedDeviceName); - connected_trusted_not_paired->SetString("pairingMethod", ""); - connected_trusted_not_paired->SetString("pairingAuthToken", ""); - connected_trusted_not_paired->SetString("pairingAction", ""); - connected_trusted_not_paired->SetString("alias", - kConnectedTrustedNotPairedDeviceName); - connected_trusted_not_paired->SetInteger( - "classValue", kConnectedTrustedNotPairedDeviceClass); - connected_trusted_not_paired->SetBoolean("isTrusted", true); - connected_trusted_not_paired->SetBoolean("discoverable", true); - connected_trusted_not_paired->SetBoolean("paired", false); - connected_trusted_not_paired->SetBoolean("incoming", false); - predefined_devices->Append(std::move(connected_trusted_not_paired)); - - return predefined_devices; + connected_trusted_not_paired.SetStringKey( + "name", kConnectedTrustedNotPairedDeviceName); + connected_trusted_not_paired.SetStringKey("pairingMethod", ""); + connected_trusted_not_paired.SetStringKey("pairingAuthToken", ""); + connected_trusted_not_paired.SetStringKey("pairingAction", ""); + connected_trusted_not_paired.SetStringKey( + "alias", kConnectedTrustedNotPairedDeviceName); + connected_trusted_not_paired.SetIntKey("classValue", + kConnectedTrustedNotPairedDeviceClass); + connected_trusted_not_paired.SetBoolKey("isTrusted", true); + connected_trusted_not_paired.SetBoolKey("discoverable", true); + connected_trusted_not_paired.SetBoolKey("paired", false); + connected_trusted_not_paired.SetBoolKey("incoming", false); + predefined_devices.push_back(std::move(connected_trusted_not_paired)); + + return base::Value(std::move(predefined_devices)); } void FakeBluetoothDeviceClient::RemoveDevice( diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_device_client.h b/chromium/device/bluetooth/dbus/fake_bluetooth_device_client.h index 66db5320ea0..98a9676382a 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_device_client.h +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_device_client.h @@ -140,7 +140,7 @@ class DEVICE_BLUETOOTH_EXPORT FakeBluetoothDeviceClient // Creates and returns a list of std::unique_ptr<base::DictionaryValue> // objects, which contain all the data from the constants for devices with // predefined behavior. - std::unique_ptr<base::ListValue> GetBluetoothDevicesAsDictionaries() const; + base::Value GetBluetoothDevicesAsDictionaries() const; SimulatedPairingOptions* GetPairingOptions( const dbus::ObjectPath& object_path); diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_characteristic_client.cc b/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_characteristic_client.cc index ab1c641f56b..41e00e93a88 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_characteristic_client.cc +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_characteristic_client.cc @@ -6,6 +6,7 @@ #include "base/bind.h" #include "base/location.h" +#include "base/logging.h" #include "base/rand_util.h" #include "base/single_thread_task_runner.h" #include "base/threading/thread_task_runner_handle.h" @@ -206,6 +207,7 @@ void FakeBluetoothGattCharacteristicClient::ReadValue( void FakeBluetoothGattCharacteristicClient::WriteValue( const dbus::ObjectPath& object_path, const std::vector<uint8_t>& value, + base::StringPiece type_option, base::OnceClosure callback, ErrorCallback error_callback) { if (!authenticated_) { 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 217636c5389..bb17ba3f958 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_characteristic_client.h +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_characteristic_client.h @@ -57,6 +57,7 @@ class DEVICE_BLUETOOTH_EXPORT FakeBluetoothGattCharacteristicClient ErrorCallback error_callback) override; void WriteValue(const dbus::ObjectPath& object_path, const std::vector<uint8_t>& value, + base::StringPiece type_option, base::OnceClosure callback, ErrorCallback error_callback) override; void PrepareWriteValue(const dbus::ObjectPath& object_path, diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_service_client.cc b/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_service_client.cc index 5441071b4bd..1dfd30aa534 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_service_client.cc +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_gatt_service_client.cc @@ -6,6 +6,7 @@ #include "base/bind.h" #include "base/location.h" +#include "base/logging.h" #include "base/single_thread_task_runner.h" #include "base/threading/thread_task_runner_handle.h" #include "base/time/time.h" diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_le_advertisement_service_provider.cc b/chromium/device/bluetooth/dbus/fake_bluetooth_le_advertisement_service_provider.cc index 52d3239f925..a4a3ba97129 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_le_advertisement_service_provider.cc +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_le_advertisement_service_provider.cc @@ -4,6 +4,7 @@ #include "device/bluetooth/dbus/fake_bluetooth_le_advertisement_service_provider.h" +#include "base/logging.h" #include "device/bluetooth/dbus/bluez_dbus_manager.h" #include "device/bluetooth/dbus/fake_bluetooth_le_advertising_manager_client.h" diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_media_client.cc b/chromium/device/bluetooth/dbus/fake_bluetooth_media_client.cc index 7e67ac7a86e..fd693399dd6 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_media_client.cc +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_media_client.cc @@ -6,6 +6,7 @@ #include <string> +#include "base/logging.h" #include "base/stl_util.h" #include "device/bluetooth/dbus/bluez_dbus_manager.h" #include "device/bluetooth/dbus/fake_bluetooth_adapter_client.h" diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_media_endpoint_service_provider.cc b/chromium/device/bluetooth/dbus/fake_bluetooth_media_endpoint_service_provider.cc index faf04dc0363..4a0b7c33695 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_media_endpoint_service_provider.cc +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_media_endpoint_service_provider.cc @@ -4,6 +4,7 @@ #include "device/bluetooth/dbus/fake_bluetooth_media_endpoint_service_provider.h" +#include "base/logging.h" #include "device/bluetooth/dbus/bluez_dbus_manager.h" #include "device/bluetooth/dbus/fake_bluetooth_media_client.h" #include "device/bluetooth/dbus/fake_bluetooth_media_transport_client.h" diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_media_endpoint_service_provider.h b/chromium/device/bluetooth/dbus/fake_bluetooth_media_endpoint_service_provider.h index f907e4da732..43b72422101 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_media_endpoint_service_provider.h +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_media_endpoint_service_provider.h @@ -9,7 +9,6 @@ #include <vector> -#include "base/logging.h" #include "base/macros.h" #include "dbus/object_path.h" #include "device/bluetooth/bluetooth_export.h" diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_media_transport_client.cc b/chromium/device/bluetooth/dbus/fake_bluetooth_media_transport_client.cc index d7b267d8cc7..7d4c63a468e 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_media_transport_client.cc +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_media_transport_client.cc @@ -13,6 +13,7 @@ #include "base/bind.h" #include "base/file_descriptor_posix.h" +#include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/stl_util.h" #include "device/bluetooth/dbus/bluetooth_media_client.h" diff --git a/chromium/device/bluetooth/dbus/fake_bluetooth_profile_service_provider.cc b/chromium/device/bluetooth/dbus/fake_bluetooth_profile_service_provider.cc index f684ecb112a..508e833fb3e 100644 --- a/chromium/device/bluetooth/dbus/fake_bluetooth_profile_service_provider.cc +++ b/chromium/device/bluetooth/dbus/fake_bluetooth_profile_service_provider.cc @@ -7,6 +7,7 @@ #include <memory> #include <utility> +#include "base/logging.h" #include "device/bluetooth/dbus/bluez_dbus_manager.h" #include "device/bluetooth/dbus/fake_bluetooth_profile_manager_client.h" diff --git a/chromium/device/bluetooth/device.cc b/chromium/device/bluetooth/device.cc index d155f38ee65..fd3df58d5de 100644 --- a/chromium/device/bluetooth/device.cc +++ b/chromium/device/bluetooth/device.cc @@ -180,7 +180,7 @@ void Device::WriteValueForCharacteristic( } auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback)); - characteristic->WriteRemoteCharacteristic( + characteristic->DeprecatedWriteRemoteCharacteristic( value, base::BindOnce(&Device::OnWriteRemoteCharacteristic, weak_ptr_factory_.GetWeakPtr(), copyable_callback), diff --git a/chromium/device/bluetooth/public/mojom/BUILD.gn b/chromium/device/bluetooth/public/mojom/BUILD.gn index beed96c8d44..a8229236120 100644 --- a/chromium/device/bluetooth/public/mojom/BUILD.gn +++ b/chromium/device/bluetooth/public/mojom/BUILD.gn @@ -58,7 +58,10 @@ mojom("deprecated_experimental_interfaces") { # Implementation of the mojom interfaces: "//device/bluetooth:deprecated_experimental_mojo", - # Single approved client of the interfaces: + # Bluetooth internals page "//chrome/browser/ui/webui/bluetooth_internals:*", + + # Nearby Sharing feature + "//chrome/services/sharing/public/mojom:*", ] } diff --git a/chromium/device/bluetooth/public/mojom/test/fake_bluetooth.mojom b/chromium/device/bluetooth/public/mojom/test/fake_bluetooth.mojom index 710be64b77d..cd133119570 100644 --- a/chromium/device/bluetooth/public/mojom/test/fake_bluetooth.mojom +++ b/chromium/device/bluetooth/public/mojom/test/fake_bluetooth.mojom @@ -38,6 +38,14 @@ enum CentralState { POWERED_OFF, }; +// GATT characteristic write type. +enum WriteType { + kNone, + kWriteDefaultDeprecated, + kWriteWithResponse, + kWriteWithoutResponse, +}; + // Stores the external appearance description of the device. struct Appearance { bool has_value; @@ -323,7 +331,8 @@ interface FakeCentral { GetLastWrittenCharacteristicValue( string characteristic_id, string service_id, - string peripheral_address) => (bool success, array<uint8>? value); + string peripheral_address) => (bool success, array<uint8>? value, + WriteType write_type); // Sets the next read response for descriptor with |descriptor_id| in // |characteristics_id| in |service_id| and in |peripheral_address| to |code| diff --git a/chromium/device/fido/BUILD.gn b/chromium/device/fido/BUILD.gn index b1867a8b1b6..686c8bab4ac 100644 --- a/chromium/device/fido/BUILD.gn +++ b/chromium/device/fido/BUILD.gn @@ -8,114 +8,29 @@ import("//third_party/protobuf/proto_library.gni") component("fido") { sources = [ - "attestation_object.cc", - "attestation_object.h", - "attestation_statement.cc", - "attestation_statement.h", - "attestation_statement_formats.cc", - "attestation_statement_formats.h", "attested_credential_data.cc", "attested_credential_data.h", - "authenticator_data.cc", - "authenticator_data.h", - "authenticator_get_assertion_response.cc", - "authenticator_get_assertion_response.h", - "authenticator_get_info_response.cc", - "authenticator_get_info_response.h", - "authenticator_make_credential_response.cc", - "authenticator_make_credential_response.h", "authenticator_selection_criteria.cc", "authenticator_selection_criteria.h", - "authenticator_supported_options.cc", - "authenticator_supported_options.h", - "bio/enroller.cc", - "bio/enroller.h", - "bio/enrollment.cc", - "bio/enrollment.h", - "bio/enrollment_handler.cc", - "bio/enrollment_handler.h", - "ble_adapter_manager.cc", - "ble_adapter_manager.h", + "cable/cable_discovery_data.cc", "cable/cable_discovery_data.h", - "cable/fido_ble_connection.cc", - "cable/fido_ble_connection.h", - "cable/fido_ble_frames.cc", - "cable/fido_ble_frames.h", - "cable/fido_ble_transaction.cc", - "cable/fido_ble_transaction.h", - "cable/fido_ble_uuids.cc", - "cable/fido_ble_uuids.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", "cable/noise.cc", "cable/noise.h", "cable/v2_handshake.cc", "cable/v2_handshake.h", "cbor_extract.cc", - "client_data.cc", - "client_data.h", - "credential_management.cc", - "credential_management.h", - "credential_management_handler.cc", - "credential_management_handler.h", - "ctap2_device_operation.h", - "ctap_empty_authenticator_request.cc", - "ctap_empty_authenticator_request.h", - "ctap_get_assertion_request.cc", - "ctap_get_assertion_request.h", - "ctap_make_credential_request.cc", - "ctap_make_credential_request.h", - "device_operation.h", - "device_response_converter.cc", - "device_response_converter.h", + "ed25519_public_key.cc", + "ed25519_public_key.h", "features.cc", "features.h", - "fido_authenticator.cc", - "fido_authenticator.h", "fido_constants.cc", "fido_constants.h", - "fido_device.cc", - "fido_device.h", - "fido_device_authenticator.cc", - "fido_device_authenticator.h", - "fido_device_discovery.cc", - "fido_device_discovery.h", - "fido_discovery_base.cc", - "fido_discovery_base.h", - "fido_discovery_factory.cc", - "fido_discovery_factory.h", "fido_parsing_utils.cc", "fido_parsing_utils.h", - "fido_request_handler_base.cc", - "fido_request_handler_base.h", - "fido_task.cc", - "fido_task.h", "fido_transport_protocol.cc", "fido_transport_protocol.h", - "fido_types.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", - "make_credential_task.h", - "opaque_attestation_statement.cc", - "opaque_attestation_statement.h", "p256_public_key.cc", "p256_public_key.h", - "pin.cc", - "pin.h", - "platform_credential_store.h", "public_key.cc", "public_key.h", "public_key_credential_descriptor.cc", @@ -126,26 +41,8 @@ component("fido") { "public_key_credential_rp_entity.h", "public_key_credential_user_entity.cc", "public_key_credential_user_entity.h", - "reset_request_handler.cc", - "reset_request_handler.h", - "response_data.cc", - "response_data.h", "rsa_public_key.cc", "rsa_public_key.h", - "set_pin_request_handler.cc", - "set_pin_request_handler.h", - "u2f_command_constructor.cc", - "u2f_command_constructor.h", - "u2f_register_operation.cc", - "u2f_register_operation.h", - "u2f_sign_operation.cc", - "u2f_sign_operation.h", - "virtual_ctap2_device.cc", - "virtual_ctap2_device.h", - "virtual_fido_device.cc", - "virtual_fido_device.h", - "virtual_u2f_device.cc", - "virtual_u2f_device.h", ] defines = [ "IS_DEVICE_FIDO_IMPL" ] @@ -171,13 +68,119 @@ component("fido") { libs = [] # Extended for mac. - # HID is not supported on Android. + # Android implementation of FIDO is delegated to GMSCore. if (!is_android) { sources += [ + "attestation_object.cc", + "attestation_object.h", + "attestation_statement.cc", + "attestation_statement.h", + "attestation_statement_formats.cc", + "attestation_statement_formats.h", + "authenticator_data.cc", + "authenticator_data.h", + "authenticator_get_assertion_response.cc", + "authenticator_get_assertion_response.h", + "authenticator_get_info_response.cc", + "authenticator_get_info_response.h", + "authenticator_make_credential_response.cc", + "authenticator_make_credential_response.h", + "authenticator_supported_options.cc", + "authenticator_supported_options.h", + "bio/enroller.cc", + "bio/enroller.h", + "bio/enrollment.cc", + "bio/enrollment.h", + "bio/enrollment_handler.cc", + "bio/enrollment_handler.h", + "ble_adapter_manager.cc", + "ble_adapter_manager.h", + "cable/fido_ble_connection.cc", + "cable/fido_ble_connection.h", + "cable/fido_ble_frames.cc", + "cable/fido_ble_frames.h", + "cable/fido_ble_transaction.cc", + "cable/fido_ble_transaction.h", + "cable/fido_ble_uuids.cc", + "cable/fido_ble_uuids.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", + "client_data.cc", + "client_data.h", + "credential_management.cc", + "credential_management.h", + "credential_management_handler.cc", + "credential_management_handler.h", + "ctap2_device_operation.h", + "ctap_empty_authenticator_request.cc", + "ctap_empty_authenticator_request.h", + "ctap_get_assertion_request.cc", + "ctap_get_assertion_request.h", + "ctap_make_credential_request.cc", + "ctap_make_credential_request.h", + "device_operation.h", + "device_response_converter.cc", + "device_response_converter.h", + "fido_authenticator.cc", + "fido_authenticator.h", + "fido_device.cc", + "fido_device.h", + "fido_device_authenticator.cc", + "fido_device_authenticator.h", + "fido_device_discovery.cc", + "fido_device_discovery.h", + "fido_discovery_base.cc", + "fido_discovery_base.h", + "fido_discovery_factory.cc", + "fido_discovery_factory.h", + "fido_request_handler_base.cc", + "fido_request_handler_base.h", + "fido_task.cc", + "fido_task.h", + "fido_types.h", + "get_assertion_request_handler.cc", + "get_assertion_request_handler.h", + "get_assertion_task.cc", + "get_assertion_task.h", "hid/fido_hid_device.cc", "hid/fido_hid_device.h", "hid/fido_hid_discovery.cc", "hid/fido_hid_discovery.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", + "make_credential_task.h", + "opaque_attestation_statement.cc", + "opaque_attestation_statement.h", + "pin.cc", + "pin.h", + "platform_credential_store.h", + "reset_request_handler.cc", + "reset_request_handler.h", + "response_data.cc", + "response_data.h", + "set_pin_request_handler.cc", + "set_pin_request_handler.h", + "u2f_command_constructor.cc", + "u2f_command_constructor.h", + "u2f_register_operation.cc", + "u2f_register_operation.h", + "u2f_sign_operation.cc", + "u2f_sign_operation.h", + "virtual_ctap2_device.cc", + "virtual_ctap2_device.h", + "virtual_fido_device.cc", + "virtual_fido_device.h", + "virtual_u2f_device.cc", + "virtual_u2f_device.h", ] deps += [ @@ -336,13 +339,7 @@ is_linux_without_udev = is_linux && !use_udev source_set("test_support") { testonly = true - sources = [ - "fake_fido_discovery.cc", - "fake_fido_discovery.h", - "test_callback_receiver.h", - "virtual_fido_device_factory.cc", - "virtual_fido_device_factory.h", - ] + sources = [ "test_callback_receiver.h" ] deps = [ "//base", "//components/apdu", @@ -356,11 +353,20 @@ source_set("test_support") { # Android doesn't compile. Linux requires udev. if (!is_linux_without_udev && !is_android) { sources += [ + "fake_fido_discovery.cc", + "fake_fido_discovery.h", "hid/fake_hid_impl_for_testing.cc", "hid/fake_hid_impl_for_testing.h", ] } + if (!is_android) { + sources += [ + "virtual_fido_device_factory.cc", + "virtual_fido_device_factory.h", + ] + } + if (is_mac) { sources += [ "mac/fake_keychain.h", diff --git a/chromium/device/fido/attestation_statement_formats_unittest.cc b/chromium/device/fido/attestation_statement_formats_unittest.cc index f7f4d0b666b..809fac65265 100644 --- a/chromium/device/fido/attestation_statement_formats_unittest.cc +++ b/chromium/device/fido/attestation_statement_formats_unittest.cc @@ -84,7 +84,7 @@ constexpr uint8_t kCertificates[] = { TEST(PackedAttestationStatementTest, CBOR) { EXPECT_THAT( *cbor::Writer::Write(AsCBOR(PackedAttestationStatement( - CoseAlgorithmIdentifier::kCoseEs256, + CoseAlgorithmIdentifier::kEs256, fido_parsing_utils::Materialize(kSignature), {fido_parsing_utils::Materialize(kCertificates)}))), testing::ElementsAreArray(test_data::kPackedAttestationStatementCBOR)); @@ -92,7 +92,7 @@ TEST(PackedAttestationStatementTest, CBOR) { TEST(PackedAttestationStatementTest, CBOR_NoCerts) { EXPECT_THAT(*cbor::Writer::Write(AsCBOR(PackedAttestationStatement( - CoseAlgorithmIdentifier::kCoseEs256, + CoseAlgorithmIdentifier::kEs256, fido_parsing_utils::Materialize(kSignature), {}))), testing::ElementsAreArray( test_data::kPackedAttestationStatementCBORNoCerts)); diff --git a/chromium/device/fido/attested_credential_data.cc b/chromium/device/fido/attested_credential_data.cc index e1c4f9e6789..1365828d755 100644 --- a/chromium/device/fido/attested_credential_data.cc +++ b/chromium/device/fido/attested_credential_data.cc @@ -10,12 +10,19 @@ #include "base/numerics/safe_math.h" #include "components/cbor/reader.h" #include "components/device_event_log/device_event_log.h" +#include "device/fido/cbor_extract.h" +#include "device/fido/ed25519_public_key.h" #include "device/fido/fido_constants.h" #include "device/fido/fido_parsing_utils.h" #include "device/fido/p256_public_key.h" #include "device/fido/public_key.h" #include "device/fido/rsa_public_key.h" +using device::cbor_extract::IntKey; +using device::cbor_extract::Is; +using device::cbor_extract::StepOrByte; +using device::cbor_extract::Stop; + namespace device { // static @@ -57,70 +64,88 @@ AttestedCredentialData::ConsumeFromCtapResponse( const cbor::Value::MapValue& public_key_map = public_key_cbor->GetMap(); - // kAlg is required to be present. See - // https://www.w3.org/TR/webauthn/#credentialpublickey - // COSE allows it to be a string or an integer. However, WebAuthn defines - // COSEAlgorithmIdentifier to be a long[1], thus only integer-based algorithms - // can be negotiated. - // - // [1] https://www.w3.org/TR/webauthn/#alg-identifier - const auto it = - public_key_map.find(cbor::Value(static_cast<int64_t>(CoseKeyKey::kAlg))); - if (it == public_key_map.end() || !it->second.is_integer()) { - FIDO_LOG(ERROR) << "Public key is missing algorithm identifier"; + struct COSEKey { + const int64_t* alg; + const int64_t* kty; + } cose_key; + + static constexpr cbor_extract::StepOrByte<COSEKey> kSteps[] = { + // clang-format off + + // kAlg is required to be present. See + // https://www.w3.org/TR/webauthn/#credentialpublickey + // COSE allows it to be a string or an integer. However, WebAuthn defines + // COSEAlgorithmIdentifier to be a long[1], thus only integer-based + // algorithms can be negotiated. + // + // [1] https://www.w3.org/TR/webauthn/#alg-identifier + ELEMENT(Is::kRequired, COSEKey, alg), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kAlg)), + + // kKty is required in COSE keys: + // https://tools.ietf.org/html/rfc8152#section-7 + ELEMENT(Is::kRequired, COSEKey, kty), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kKty)), + + Stop<COSEKey>(), + // clang-format on + }; + + if (!cbor_extract::Extract<COSEKey>(&cose_key, kSteps, public_key_map)) { + FIDO_LOG(ERROR) << "Failed to parse COSE key"; return base::nullopt; } // In WebIDL, a |long| is an |int32_t|[1]. // // [1] https://heycam.github.io/webidl/#idl-long - const int64_t algorithm64 = it->second.GetInteger(); + const int64_t algorithm64 = *cose_key.alg; if (algorithm64 > std::numeric_limits<int32_t>::max() || algorithm64 < std::numeric_limits<int32_t>::min()) { FIDO_LOG(ERROR) << "COSE algorithm in public key is out of range"; return base::nullopt; } const int32_t algorithm = static_cast<int32_t>(algorithm64); + const int64_t key_type = *cose_key.kty; std::unique_ptr<PublicKey> public_key; - // kKty is required in COSE keys: - // https://tools.ietf.org/html/rfc8152#section-7 - auto public_key_type = - public_key_map.find(cbor::Value(static_cast<int64_t>(CoseKeyKey::kKty))); - if (public_key_type == public_key_map.end()) { - FIDO_LOG(ERROR) << "COSE key missing kty"; - return base::nullopt; - } + if (key_type == static_cast<int64_t>(CoseKeyTypes::kEC2) || + key_type == static_cast<int64_t>(CoseKeyTypes::kOKP)) { + auto curve = public_key_map.find( + cbor::Value(static_cast<int64_t>(CoseKeyKey::kEllipticCurve))); + if (curve == public_key_map.end() || !curve->second.is_integer()) { + return base::nullopt; + } + const int64_t curve_id = curve->second.GetInteger(); - if (public_key_type->second.is_unsigned()) { - const int64_t key_type = public_key_type->second.GetUnsigned(); - if (key_type == static_cast<int64_t>(CoseKeyTypes::kEC2)) { - auto curve = public_key_map.find( - cbor::Value(static_cast<int64_t>(CoseKeyKey::kEllipticCurve))); - if (curve == public_key_map.end() || !curve->second.is_integer()) { + if (key_type == static_cast<int64_t>(CoseKeyTypes::kEC2) && + curve_id == static_cast<int64_t>(CoseCurves::kP256)) { + auto p256_key = P256PublicKey::ExtractFromCOSEKey( + algorithm, public_key_cbor_bytes, public_key_map); + if (!p256_key) { + FIDO_LOG(ERROR) << "Invalid P-256 public key"; return base::nullopt; } - - if (curve->second.GetInteger() == - static_cast<int64_t>(CoseCurves::kP256)) { - auto p256_key = P256PublicKey::ExtractFromCOSEKey( - algorithm, public_key_cbor_bytes, public_key_map); - if (!p256_key) { - FIDO_LOG(ERROR) << "Invalid P-256 public key"; - return base::nullopt; - } - public_key = std::move(p256_key); - } - } else if (key_type == static_cast<int64_t>(CoseKeyTypes::kRSA)) { - auto rsa_key = RSAPublicKey::ExtractFromCOSEKey( + public_key = std::move(p256_key); + } else if (key_type == static_cast<int64_t>(CoseKeyTypes::kOKP) && + curve_id == static_cast<int64_t>(CoseCurves::kEd25519)) { + auto ed25519_key = Ed25519PublicKey::ExtractFromCOSEKey( algorithm, public_key_cbor_bytes, public_key_map); - if (!rsa_key) { - FIDO_LOG(ERROR) << "Invalid RSA public key"; + if (!ed25519_key) { + FIDO_LOG(ERROR) << "Invalid Ed25519 public key"; return base::nullopt; } - public_key = std::move(rsa_key); + public_key = std::move(ed25519_key); + } + } else if (key_type == static_cast<int64_t>(CoseKeyTypes::kRSA)) { + auto rsa_key = RSAPublicKey::ExtractFromCOSEKey( + algorithm, public_key_cbor_bytes, public_key_map); + if (!rsa_key) { + FIDO_LOG(ERROR) << "Invalid RSA public key"; + return base::nullopt; } + public_key = std::move(rsa_key); } if (!public_key) { @@ -204,7 +229,7 @@ std::vector<uint8_t> AttestedCredentialData::SerializeAsBytes() const { fido_parsing_utils::Append(&attestation_data, aaguid_); fido_parsing_utils::Append(&attestation_data, credential_id_length_); fido_parsing_utils::Append(&attestation_data, credential_id_); - fido_parsing_utils::Append(&attestation_data, public_key_->cose_key_bytes()); + fido_parsing_utils::Append(&attestation_data, public_key_->cose_key_bytes); return attestation_data; } diff --git a/chromium/device/fido/attested_credential_data.h b/chromium/device/fido/attested_credential_data.h index 1999430ab54..a78c334c76c 100644 --- a/chromium/device/fido/attested_credential_data.h +++ b/chromium/device/fido/attested_credential_data.h @@ -18,7 +18,7 @@ namespace device { -class PublicKey; +struct PublicKey; // https://www.w3.org/TR/2017/WD-webauthn-20170505/#sec-attestation-data class COMPONENT_EXPORT(DEVICE_FIDO) AttestedCredentialData { diff --git a/chromium/device/fido/authenticator_data.cc b/chromium/device/fido/authenticator_data.cc index b6681e6990f..09479b23a31 100644 --- a/chromium/device/fido/authenticator_data.cc +++ b/chromium/device/fido/authenticator_data.cc @@ -21,6 +21,26 @@ namespace { constexpr size_t kAttestedCredentialDataOffset = kRpIdHashLength + kFlagsLength + kSignCounterLength; +uint8_t AuthenticatorDataFlags(bool user_present, + bool user_verified, + bool has_attested_credential_data, + bool has_extension_data) { + return (user_present ? base::strict_cast<uint8_t>( + AuthenticatorData::Flag::kTestOfUserPresence) + : 0) | + (user_verified ? base::strict_cast<uint8_t>( + AuthenticatorData::Flag::kTestOfUserVerification) + : 0) | + (has_attested_credential_data + ? base::strict_cast<uint8_t>( + AuthenticatorData::Flag::kAttestation) + : 0) | + (has_extension_data + ? base::strict_cast<uint8_t>( + AuthenticatorData::Flag::kExtensionDataIncluded) + : 0); +} + } // namespace // static @@ -71,13 +91,12 @@ base::Optional<AuthenticatorData> AuthenticatorData::DecodeAuthenticatorData( } AuthenticatorData::AuthenticatorData( - base::span<const uint8_t, kRpIdHashLength> application_parameter, + base::span<const uint8_t, kRpIdHashLength> rp_id_hash, uint8_t flags, base::span<const uint8_t, kSignCounterLength> counter, base::Optional<AttestedCredentialData> data, base::Optional<cbor::Value> extensions) - : application_parameter_( - fido_parsing_utils::Materialize(application_parameter)), + : application_parameter_(fido_parsing_utils::Materialize(rp_id_hash)), flags_(flags), counter_(fido_parsing_utils::Materialize(counter)), attested_data_(std::move(data)), @@ -89,6 +108,25 @@ AuthenticatorData::AuthenticatorData( !!attested_data_); } +AuthenticatorData::AuthenticatorData( + base::span<const uint8_t, kRpIdHashLength> rp_id_hash, + bool user_present, + bool user_verified, + uint32_t sign_counter, + base::Optional<AttestedCredentialData> attested_credential_data, + base::Optional<cbor::Value> extensions) + : AuthenticatorData( + rp_id_hash, + AuthenticatorDataFlags(user_present, + user_verified, + attested_credential_data.has_value(), + extensions.has_value()), + std::array<uint8_t, kSignCounterLength>{ + (sign_counter >> 24) & 0xff, (sign_counter >> 16) & 0xff, + (sign_counter >> 8) & 0xff, sign_counter & 0xff}, + std::move(attested_credential_data), + std::move(extensions)) {} + AuthenticatorData::AuthenticatorData(AuthenticatorData&& other) = default; AuthenticatorData& AuthenticatorData::operator=(AuthenticatorData&& other) = default; diff --git a/chromium/device/fido/authenticator_data.h b/chromium/device/fido/authenticator_data.h index 3f4f440ab5a..8a80e3d1c81 100644 --- a/chromium/device/fido/authenticator_data.h +++ b/chromium/device/fido/authenticator_data.h @@ -38,14 +38,22 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorData { // The attested credential |data| must be specified iff |flags| have // kAttestation set; and |extensions| must be specified iff |flags| have // kExtensionDataIncluded set. + AuthenticatorData(base::span<const uint8_t, kRpIdHashLength> rp_id_hash, + uint8_t flags, + base::span<const uint8_t, kSignCounterLength> sign_counter, + base::Optional<AttestedCredentialData> data, + base::Optional<cbor::Value> extensions = base::nullopt); + + // Creates an AuthenticatorData with flags and signature counter encoded + // according to the supplied arguments. AuthenticatorData( - base::span<const uint8_t, kRpIdHashLength> application_parameter, - uint8_t flags, - base::span<const uint8_t, kSignCounterLength> counter, - base::Optional<AttestedCredentialData> data, - base::Optional<cbor::Value> extensions = base::nullopt); + base::span<const uint8_t, kRpIdHashLength> rp_id_hash, + bool user_present, + bool user_verified, + uint32_t sign_counter, + base::Optional<AttestedCredentialData> attested_credential_data, + base::Optional<cbor::Value> extensions); - // Moveable. AuthenticatorData(AuthenticatorData&& other); AuthenticatorData& operator=(AuthenticatorData&& other); diff --git a/chromium/device/fido/authenticator_get_info_response.cc b/chromium/device/fido/authenticator_get_info_response.cc index 616fa0683bb..fa5bd5efda1 100644 --- a/chromium/device/fido/authenticator_get_info_response.cc +++ b/chromium/device/fido/authenticator_get_info_response.cc @@ -27,9 +27,14 @@ cbor::Value::ArrayValue ToArrayValue(const Container& container) { AuthenticatorGetInfoResponse::AuthenticatorGetInfoResponse( base::flat_set<ProtocolVersion> in_versions, + base::flat_set<Ctap2Version> in_ctap2_versions, base::span<const uint8_t, kAaguidLength> in_aaguid) : versions(std::move(in_versions)), - aaguid(fido_parsing_utils::Materialize(in_aaguid)) {} + ctap2_versions(std::move(in_ctap2_versions)), + aaguid(fido_parsing_utils::Materialize(in_aaguid)) { + DCHECK_NE(base::Contains(versions, ProtocolVersion::kCtap2), + ctap2_versions.empty()); +} AuthenticatorGetInfoResponse::AuthenticatorGetInfoResponse( AuthenticatorGetInfoResponse&& that) = default; @@ -44,8 +49,27 @@ std::vector<uint8_t> AuthenticatorGetInfoResponse::EncodeToCBOR( const AuthenticatorGetInfoResponse& response) { cbor::Value::ArrayValue version_array; for (const auto& version : response.versions) { - version_array.emplace_back( - version == ProtocolVersion::kCtap2 ? kCtap2Version : kU2fVersion); + switch (version) { + case ProtocolVersion::kCtap2: + for (const auto& ctap2_version : response.ctap2_versions) { + switch (ctap2_version) { + case Ctap2Version::kCtap2_0: + version_array.emplace_back(kCtap2Version); + break; + case Ctap2Version::kCtap2_1: + version_array.emplace_back(kCtap2_1Version); + break; + case Ctap2Version::kUnknown: + NOTREACHED(); + } + } + break; + case ProtocolVersion::kU2f: + version_array.emplace_back(kU2fVersion); + break; + case ProtocolVersion::kUnknown: + NOTREACHED(); + } } cbor::Value::MapValue device_info_map; device_info_map.emplace(1, std::move(version_array)); @@ -79,7 +103,12 @@ std::vector<uint8_t> AuthenticatorGetInfoResponse::EncodeToCBOR( std::vector<cbor::Value> algorithms_cbor; algorithms_cbor.reserve(response.algorithms.size()); for (const auto& algorithm : response.algorithms) { - algorithms_cbor.emplace_back(cbor::Value(algorithm)); + // Entries are PublicKeyCredentialParameters + // https://w3c.github.io/webauthn/#dictdef-publickeycredentialparameters + cbor::Value::MapValue entry; + entry.emplace("type", "public-key"); + entry.emplace("alg", algorithm); + algorithms_cbor.emplace_back(cbor::Value(entry)); } device_info_map.emplace(10, std::move(algorithms_cbor)); } diff --git a/chromium/device/fido/authenticator_get_info_response.h b/chromium/device/fido/authenticator_get_info_response.h index 95f4fde3e52..fc4149ebd99 100644 --- a/chromium/device/fido/authenticator_get_info_response.h +++ b/chromium/device/fido/authenticator_get_info_response.h @@ -26,6 +26,7 @@ namespace device { struct COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorGetInfoResponse { public: AuthenticatorGetInfoResponse(base::flat_set<ProtocolVersion> versions, + base::flat_set<Ctap2Version> in_ctap2_version, base::span<const uint8_t, kAaguidLength> aaguid); AuthenticatorGetInfoResponse(AuthenticatorGetInfoResponse&& that); AuthenticatorGetInfoResponse& operator=(AuthenticatorGetInfoResponse&& other); @@ -35,6 +36,7 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorGetInfoResponse { const AuthenticatorGetInfoResponse& response); base::flat_set<ProtocolVersion> versions; + base::flat_set<Ctap2Version> ctap2_versions; std::array<uint8_t, kAaguidLength> aaguid; base::Optional<uint32_t> max_msg_size; base::Optional<uint32_t> max_credential_count_in_list; @@ -42,7 +44,7 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorGetInfoResponse { base::Optional<std::vector<uint8_t>> pin_protocols; base::Optional<std::vector<std::string>> extensions; std::vector<int32_t> algorithms = { - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256), + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), }; AuthenticatorSupportedOptions options; diff --git a/chromium/device/fido/authenticator_make_credential_response.cc b/chromium/device/fido/authenticator_make_credential_response.cc index 8c23cd74c5c..fbdab796383 100644 --- a/chromium/device/fido/authenticator_make_credential_response.cc +++ b/chromium/device/fido/authenticator_make_credential_response.cc @@ -25,7 +25,7 @@ AuthenticatorMakeCredentialResponse::CreateFromU2fRegisterResponse( base::span<const uint8_t, kRpIdHashLength> relying_party_id_hash, base::span<const uint8_t> u2f_data) { auto public_key = P256PublicKey::ExtractFromU2fRegistrationResponse( - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256), u2f_data); + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), u2f_data); if (!public_key) return base::nullopt; diff --git a/chromium/device/fido/authenticator_supported_options.cc b/chromium/device/fido/authenticator_supported_options.cc index 27b266ef6e1..92fc0e49b4f 100644 --- a/chromium/device/fido/authenticator_supported_options.cc +++ b/chromium/device/fido/authenticator_supported_options.cc @@ -83,7 +83,7 @@ cbor::Value AsCBOR(const AuthenticatorSupportedOptions& options) { break; } - if (options.supports_uv_token) { + if (options.supports_pin_uv_auth_token) { option_map.emplace(kUvTokenMapKey, true); } diff --git a/chromium/device/fido/authenticator_supported_options.h b/chromium/device/fido/authenticator_supported_options.h index 72567d9fa80..7469c018bb9 100644 --- a/chromium/device/fido/authenticator_supported_options.h +++ b/chromium/device/fido/authenticator_supported_options.h @@ -86,9 +86,9 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorSupportedOptions { // authenticator. ClientPinAvailability client_pin_availability = ClientPinAvailability::kNotSupported; - // Indicates whether the authenticator is capable of handling built in user - // verification based tokens. - bool supports_uv_token = false; + // Indicates whether the authenticator supports CTAP 2.1 pinUvAuthToken for + // establishing user verification via client PIN or a built-in sensor. + bool supports_pin_uv_auth_token = false; // Indicates whether the authenticator supports an extension for passing // information from the collectedClientData structure with a CTAP request. bool supports_android_client_data_ext = false; diff --git a/chromium/device/fido/bio/enrollment_handler.cc b/chromium/device/fido/bio/enrollment_handler.cc index d3e03a5da50..91264d427be 100644 --- a/chromium/device/fido/bio/enrollment_handler.cc +++ b/chromium/device/fido/bio/enrollment_handler.cc @@ -9,6 +9,7 @@ #include "components/device_event_log/device_event_log.h" #include "device/fido/fido_authenticator.h" #include "device/fido/fido_constants.h" +#include "device/fido/pin.h" namespace device { @@ -196,8 +197,10 @@ void BioEnrollmentHandler::OnHavePIN(std::string pin) { DCHECK_EQ(state_, State::kWaitingForPIN); state_ = State::kGettingPINToken; authenticator_->GetPINToken( - std::move(pin), base::BindOnce(&BioEnrollmentHandler::OnHavePINToken, - weak_factory_.GetWeakPtr())); + std::move(pin), {pin::Permissions::kBioEnrollment}, + /*rp_id=*/base::nullopt, + base::BindOnce(&BioEnrollmentHandler::OnHavePINToken, + weak_factory_.GetWeakPtr())); } void BioEnrollmentHandler::OnHavePINToken( diff --git a/chromium/device/fido/bio/enrollment_handler.h b/chromium/device/fido/bio/enrollment_handler.h index 32672020044..439d3d1fe63 100644 --- a/chromium/device/fido/bio/enrollment_handler.h +++ b/chromium/device/fido/bio/enrollment_handler.h @@ -18,7 +18,6 @@ #include "device/fido/fido_constants.h" #include "device/fido/fido_discovery_factory.h" #include "device/fido/fido_request_handler_base.h" -#include "device/fido/pin.h" namespace device { diff --git a/chromium/device/fido/ble_adapter_manager_unittest.cc b/chromium/device/fido/ble_adapter_manager_unittest.cc index 5267ce8fea2..37e61ea2326 100644 --- a/chromium/device/fido/ble_adapter_manager_unittest.cc +++ b/chromium/device/fido/ble_adapter_manager_unittest.cc @@ -95,6 +95,9 @@ class FidoBleAdapterManagerTest : public ::testing::Test { public: FidoBleAdapterManagerTest() { BluetoothAdapterFactory::SetAdapterForTesting(adapter_); + bluetooth_config_ = + BluetoothAdapterFactory::Get()->InitGlobalValuesForTesting(); + bluetooth_config_->SetLESupported(true); fido_discovery_factory_->ForgeNextCableDiscovery( test::FakeFidoDiscovery::StartMode::kAutomatic); @@ -133,6 +136,8 @@ class FidoBleAdapterManagerTest : public ::testing::Test { std::make_unique<test::FakeFidoDiscoveryFactory>(); std::unique_ptr<FakeFidoRequestHandlerBase> fake_request_handler_; + std::unique_ptr<BluetoothAdapterFactory::GlobalValuesForTesting> + bluetooth_config_; }; TEST_F(FidoBleAdapterManagerTest, AdapterNotPresent) { diff --git a/chromium/device/fido/cable/cable_discovery_data.cc b/chromium/device/fido/cable/cable_discovery_data.cc new file mode 100644 index 00000000000..81dd67fef59 --- /dev/null +++ b/chromium/device/fido/cable/cable_discovery_data.cc @@ -0,0 +1,253 @@ +// Copyright 2020 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/cable/cable_discovery_data.h" + +#include <cstring> + +#include "base/time/time.h" +#include "crypto/random.h" +#include "device/fido/fido_parsing_utils.h" +#include "third_party/boringssl/src/include/openssl/aes.h" +#include "third_party/boringssl/src/include/openssl/digest.h" +#include "third_party/boringssl/src/include/openssl/ec.h" +#include "third_party/boringssl/src/include/openssl/hkdf.h" +#include "third_party/boringssl/src/include/openssl/mem.h" +#include "third_party/boringssl/src/include/openssl/obj.h" + +namespace device { + +namespace { + +enum class QRValue : uint8_t { + QR_SECRET = 0, + IDENTITY_KEY_SEED = 1, +}; + +void DeriveQRValue(base::span<const uint8_t, 32> qr_generator_key, + const int64_t tick, + QRValue type, + base::span<uint8_t> out) { + uint8_t hkdf_input[sizeof(uint64_t) + 1]; + memcpy(hkdf_input, &tick, sizeof(uint64_t)); + hkdf_input[sizeof(uint64_t)] = base::strict_cast<uint8_t>(type); + + bool ok = HKDF(out.data(), out.size(), EVP_sha256(), qr_generator_key.data(), + qr_generator_key.size(), + /*salt=*/nullptr, 0, hkdf_input, sizeof(hkdf_input)); + DCHECK(ok); +} + +} // namespace + +CableDiscoveryData::CableDiscoveryData() = default; + +CableDiscoveryData::CableDiscoveryData( + CableDiscoveryData::Version version, + const CableEidArray& client_eid, + const CableEidArray& authenticator_eid, + const CableSessionPreKeyArray& session_pre_key) + : version(version) { + CHECK_EQ(Version::V1, version); + v1.emplace(); + v1->client_eid = client_eid; + v1->authenticator_eid = authenticator_eid; + v1->session_pre_key = session_pre_key; +} + +CableDiscoveryData::CableDiscoveryData( + base::span<const uint8_t, kCableQRSecretSize> qr_secret, + base::span<const uint8_t, kCableIdentityKeySeedSize> identity_key_seed) { + InitFromQRSecret(qr_secret); + v2->local_identity_seed = fido_parsing_utils::Materialize(identity_key_seed); +} + +// static +base::Optional<CableDiscoveryData> CableDiscoveryData::FromQRData( + base::span<const uint8_t, + kCableCompressedPublicKeySize + kCableQRSecretSize> qr_data) { + auto qr_secret = qr_data.subspan(kCableCompressedPublicKeySize); + CableDiscoveryData discovery_data; + discovery_data.InitFromQRSecret(base::span<const uint8_t, kCableQRSecretSize>( + qr_secret.data(), qr_secret.size())); + + bssl::UniquePtr<EC_GROUP> p256( + EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); + bssl::UniquePtr<EC_POINT> point(EC_POINT_new(p256.get())); + if (!EC_POINT_oct2point(p256.get(), point.get(), qr_data.data(), + kCableCompressedPublicKeySize, /*ctx=*/nullptr)) { + return base::nullopt; + } + CableAuthenticatorIdentityKey& identity_key = + discovery_data.v2->peer_identity.emplace(); + CHECK_EQ(identity_key.size(), + EC_POINT_point2oct( + p256.get(), point.get(), POINT_CONVERSION_UNCOMPRESSED, + identity_key.data(), identity_key.size(), /*ctx=*/nullptr)); + + return discovery_data; +} + +CableDiscoveryData::CableDiscoveryData(const CableDiscoveryData& data) = + default; + +CableDiscoveryData& CableDiscoveryData::operator=( + const CableDiscoveryData& other) = default; + +CableDiscoveryData::~CableDiscoveryData() = default; + +bool CableDiscoveryData::operator==(const CableDiscoveryData& other) const { + if (version != other.version) { + return false; + } + + switch (version) { + case CableDiscoveryData::Version::V1: + return v1->client_eid == other.v1->client_eid && + v1->authenticator_eid == other.v1->authenticator_eid && + v1->session_pre_key == other.v1->session_pre_key; + + case CableDiscoveryData::Version::V2: + return v2->eid_gen_key == other.v2->eid_gen_key && + v2->psk_gen_key == other.v2->psk_gen_key && + v2->peer_identity == other.v2->peer_identity && + v2->peer_name == other.v2->peer_name; + + case CableDiscoveryData::Version::INVALID: + CHECK(false); + return false; + } +} + +base::Optional<CableNonce> CableDiscoveryData::Match( + const CableEidArray& eid) const { + switch (version) { + case Version::V1: { + if (eid != v1->authenticator_eid) { + return base::nullopt; + } + + // The nonce is the first eight bytes of the EID. + CableNonce nonce; + const bool ok = + fido_parsing_utils::ExtractArray(v1->client_eid, 0, &nonce); + DCHECK(ok); + return nonce; + } + + case Version::V2: { + // Attempt to decrypt the EID with the EID generator key and check whether + // it has a valid structure. + AES_KEY key; + CHECK(AES_set_decrypt_key(v2->eid_gen_key.data(), + /*bits=*/8 * v2->eid_gen_key.size(), + &key) == 0); + static_assert(kCableEphemeralIdSize == AES_BLOCK_SIZE, + "EIDs are not AES blocks"); + CableEidArray decrypted; + AES_decrypt(/*in=*/eid.data(), /*out=*/decrypted.data(), &key); + const uint8_t kZeroTrailer[8] = {0}; + static_assert(8 + sizeof(kZeroTrailer) == + std::tuple_size<decltype(decrypted)>::value, + "Trailer is wrong size"); + if (CRYPTO_memcmp(kZeroTrailer, decrypted.data() + 8, + sizeof(kZeroTrailer)) != 0) { + return base::nullopt; + } + + CableNonce nonce; + static_assert( + sizeof(nonce) <= std::tuple_size<decltype(decrypted)>::value, + "nonce too large"); + memcpy(nonce.data(), decrypted.data(), sizeof(nonce)); + return nonce; + } + + case Version::INVALID: + DCHECK(false); + return base::nullopt; + } +} + +// static +QRGeneratorKey CableDiscoveryData::NewQRKey() { + QRGeneratorKey key; + crypto::RandBytes(key.data(), key.size()); + return key; +} + +// static +int64_t CableDiscoveryData::CurrentTimeTick() { + // The ticks are currently 256ms. + return base::TimeTicks::Now().since_origin().InMilliseconds() >> 8; +} + +// static +std::array<uint8_t, kCableQRSecretSize> CableDiscoveryData::DeriveQRSecret( + base::span<const uint8_t, 32> qr_generator_key, + const int64_t tick) { + std::array<uint8_t, kCableQRSecretSize> ret; + DeriveQRValue(qr_generator_key, tick, QRValue::QR_SECRET, ret); + return ret; +} + +// static +CableIdentityKeySeed CableDiscoveryData::DeriveIdentityKeySeed( + base::span<const uint8_t, 32> qr_generator_key, + const int64_t tick) { + std::array<uint8_t, kCableIdentityKeySeedSize> ret; + DeriveQRValue(qr_generator_key, tick, QRValue::IDENTITY_KEY_SEED, ret); + return ret; +} + +// static +CableQRData CableDiscoveryData::DeriveQRData( + base::span<const uint8_t, 32> qr_generator_key, + const int64_t tick) { + auto identity_key_seed = DeriveIdentityKeySeed(qr_generator_key, tick); + bssl::UniquePtr<EC_GROUP> p256( + EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); + bssl::UniquePtr<EC_KEY> identity_key(EC_KEY_derive_from_secret( + p256.get(), identity_key_seed.data(), identity_key_seed.size())); + const EC_POINT* public_key = EC_KEY_get0_public_key(identity_key.get()); + CableQRData qr_data; + static_assert( + qr_data.size() == kCableCompressedPublicKeySize + kCableQRSecretSize, + "this code needs to be updated"); + CHECK_EQ(kCableCompressedPublicKeySize, + EC_POINT_point2oct(p256.get(), public_key, + POINT_CONVERSION_COMPRESSED, qr_data.data(), + kCableCompressedPublicKeySize, /*ctx=*/nullptr)); + + auto qr_secret = CableDiscoveryData::DeriveQRSecret(qr_generator_key, tick); + memcpy(&qr_data.data()[kCableCompressedPublicKeySize], qr_secret.data(), + qr_secret.size()); + + return qr_data; +} + +CableDiscoveryData::V2Data::V2Data() = default; +CableDiscoveryData::V2Data::V2Data(const V2Data&) = default; +CableDiscoveryData::V2Data::~V2Data() = default; + +void CableDiscoveryData::InitFromQRSecret( + base::span<const uint8_t, kCableQRSecretSize> qr_secret) { + version = Version::V2; + v2.emplace(); + + static const char kEIDGen[] = "caBLE QR to EID generator key"; + bool ok = + HKDF(v2->eid_gen_key.data(), v2->eid_gen_key.size(), EVP_sha256(), + qr_secret.data(), qr_secret.size(), /*salt=*/nullptr, 0, + reinterpret_cast<const uint8_t*>(kEIDGen), sizeof(kEIDGen) - 1); + DCHECK(ok); + + static const char kPSKGen[] = "caBLE QR to PSK generator key"; + ok = HKDF(v2->psk_gen_key.data(), v2->psk_gen_key.size(), EVP_sha256(), + qr_secret.data(), qr_secret.size(), /*salt=*/nullptr, 0, + reinterpret_cast<const uint8_t*>(kPSKGen), sizeof(kPSKGen) - 1); + DCHECK(ok); +} + +} // namespace device diff --git a/chromium/device/fido/cable/cable_discovery_data.h b/chromium/device/fido/cable/cable_discovery_data.h index 8fa8a08b095..6aa17acb8ef 100644 --- a/chromium/device/fido/cable/cable_discovery_data.h +++ b/chromium/device/fido/cable/cable_discovery_data.h @@ -10,6 +10,7 @@ #include "base/component_export.h" #include "base/containers/span.h" +#include "device/fido/fido_constants.h" namespace device { @@ -39,7 +40,7 @@ using CableEidGeneratorKey = std::array<uint8_t, 32>; using CablePskGeneratorKey = std::array<uint8_t, 32>; // CableAuthenticatorIdentityKey is a P-256 public value used to authenticate a // paired phone. -using CableAuthenticatorIdentityKey = std::array<uint8_t, 65>; +using CableAuthenticatorIdentityKey = std::array<uint8_t, kP256X962Length>; using CableIdentityKeySeed = std::array<uint8_t, kCableIdentityKeySeedSize>; using CableQRData = std::array<uint8_t, kCableQRDataSize>; diff --git a/chromium/device/fido/cable/fido_ble_connection.cc b/chromium/device/fido/cable/fido_ble_connection.cc index a460fdfeff5..6b94db1640d 100644 --- a/chromium/device/fido/cable/fido_ble_connection.cc +++ b/chromium/device/fido/cable/fido_ble_connection.cc @@ -253,22 +253,20 @@ void FidoBleConnection::WriteControlPoint(const std::vector<uint8_t>& data, 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)) { - FIDO_LOG(DEBUG) << "Write without response succeeded."; - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(std::move(callback), true)); - return; - } -#endif // defined(OS_MACOSX) + // confirmed write when the characteristic does not provide the required + // property. + BluetoothRemoteGattCharacteristic::WriteType write_type = + (control_point->GetProperties() & + BluetoothRemoteGattCharacteristic::PROPERTY_WRITE_WITHOUT_RESPONSE) + ? BluetoothRemoteGattCharacteristic::WriteType::kWithoutResponse + : BluetoothRemoteGattCharacteristic::WriteType::kWithResponse; FIDO_LOG(DEBUG) << "Wrote Control Point."; auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback)); control_point->WriteRemoteCharacteristic( - data, base::BindOnce(OnWriteRemoteCharacteristic, copyable_callback), + data, write_type, + base::BindOnce(OnWriteRemoteCharacteristic, copyable_callback), base::BindOnce(OnWriteRemoteCharacteristicError, copyable_callback)); } @@ -418,6 +416,7 @@ void FidoBleConnection::WriteServiceRevision(ServiceRevision service_revision) { fido_service->GetCharacteristic(*service_revision_bitfield_id_) ->WriteRemoteCharacteristic( {static_cast<uint8_t>(service_revision)}, + BluetoothRemoteGattCharacteristic::WriteType::kWithResponse, base::BindOnce(OnWriteRemoteCharacteristic, copyable_callback), base::BindOnce(OnWriteRemoteCharacteristicError, copyable_callback)); } diff --git a/chromium/device/fido/cable/fido_ble_connection_unittest.cc b/chromium/device/fido/cable/fido_ble_connection_unittest.cc index bdd9f99a583..49f25b9f340 100644 --- a/chromium/device/fido/cable/fido_ble_connection_unittest.cc +++ b/chromium/device/fido/cable/fido_ble_connection_unittest.cc @@ -167,7 +167,8 @@ class FidoBleConnectionTest : public ::testing::Test { ON_CALL(*fido_service_revision_bitfield_, WriteRemoteCharacteristic_) .WillByDefault(Invoke( - [=](auto&, base::OnceClosure& callback, + [=](auto&, BluetoothRemoteGattCharacteristic::WriteType, + base::OnceClosure& callback, const BluetoothRemoteGattCharacteristic::ErrorCallback&) { base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, std::move(callback)); @@ -283,19 +284,15 @@ class FidoBleConnectionTest : public ::testing::Test { } 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_(_, _, _)) + EXPECT_CALL( + *fido_control_point_, + WriteRemoteCharacteristic_( + _, BluetoothRemoteGattCharacteristic::WriteType::kWithoutResponse, + _, _)) .WillOnce(Invoke([success]( - const auto& data, base::OnceClosure& callback, + const auto& data, + BluetoothRemoteGattCharacteristic::WriteType, + base::OnceClosure& callback, BluetoothRemoteGattCharacteristic::ErrorCallback& error_callback) { base::ThreadTaskRunnerHandle::Get()->PostTask( @@ -309,10 +306,15 @@ class FidoBleConnectionTest : public ::testing::Test { void SetNextWriteServiceRevisionResponse(std::vector<uint8_t> expected_data, bool success) { - EXPECT_CALL(*fido_service_revision_bitfield_, - WriteRemoteCharacteristic_(expected_data, _, _)) + EXPECT_CALL( + *fido_service_revision_bitfield_, + WriteRemoteCharacteristic_( + expected_data, + BluetoothRemoteGattCharacteristic::WriteType::kWithResponse, _, _)) .WillOnce(Invoke([success]( - const auto& data, base::OnceClosure& callback, + const auto& data, + BluetoothRemoteGattCharacteristic::WriteType, + base::OnceClosure& callback, BluetoothRemoteGattCharacteristic::ErrorCallback& error_callback) { base::ThreadTaskRunnerHandle::Get()->PostTask( @@ -337,7 +339,7 @@ class FidoBleConnectionTest : public ::testing::Test { std::make_unique<NiceMockBluetoothGattCharacteristic>( fido_service_, "fido_control_point", BluetoothUUID(kFidoControlPointUUID), kIsLocal, - BluetoothGattCharacteristic::PROPERTY_WRITE, + BluetoothGattCharacteristic::PROPERTY_WRITE_WITHOUT_RESPONSE, BluetoothGattCharacteristic::PERMISSION_NONE); fido_control_point_ = fido_control_point.get(); fido_service_->AddMockCharacteristic(std::move(fido_control_point)); diff --git a/chromium/device/fido/cable/fido_cable_discovery.cc b/chromium/device/fido/cable/fido_cable_discovery.cc index 368d657c0a9..beb8a0fec72 100644 --- a/chromium/device/fido/cable/fido_cable_discovery.cc +++ b/chromium/device/fido/cable/fido_cable_discovery.cc @@ -20,7 +20,6 @@ #include "base/time/time.h" #include "build/build_config.h" #include "components/device_event_log/device_event_log.h" -#include "crypto/random.h" #include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/bluetooth/bluetooth_advertisement.h" #include "device/bluetooth/bluetooth_discovery_session.h" @@ -29,12 +28,6 @@ #include "device/fido/cable/fido_cable_handshake_handler.h" #include "device/fido/features.h" #include "device/fido/fido_parsing_utils.h" -#include "third_party/boringssl/src/include/openssl/aes.h" -#include "third_party/boringssl/src/include/openssl/digest.h" -#include "third_party/boringssl/src/include/openssl/ec.h" -#include "third_party/boringssl/src/include/openssl/hkdf.h" -#include "third_party/boringssl/src/include/openssl/mem.h" -#include "third_party/boringssl/src/include/openssl/obj.h" namespace device { @@ -107,238 +100,8 @@ std::unique_ptr<BluetoothAdvertisement::Data> ConstructAdvertisementData( return advertisement_data; } -enum class QRValue : uint8_t { - QR_SECRET = 0, - IDENTITY_KEY_SEED = 1, -}; - -void DeriveQRValue(base::span<const uint8_t, 32> qr_generator_key, - const int64_t tick, - QRValue type, - base::span<uint8_t> out) { - uint8_t hkdf_input[sizeof(uint64_t) + 1]; - memcpy(hkdf_input, &tick, sizeof(uint64_t)); - hkdf_input[sizeof(uint64_t)] = base::strict_cast<uint8_t>(type); - - bool ok = HKDF(out.data(), out.size(), EVP_sha256(), qr_generator_key.data(), - qr_generator_key.size(), - /*salt=*/nullptr, 0, hkdf_input, sizeof(hkdf_input)); - DCHECK(ok); -} - } // namespace -// CableDiscoveryData ------------------------------------- - -CableDiscoveryData::CableDiscoveryData() = default; - -CableDiscoveryData::CableDiscoveryData( - CableDiscoveryData::Version version, - const CableEidArray& client_eid, - const CableEidArray& authenticator_eid, - const CableSessionPreKeyArray& session_pre_key) - : version(version) { - CHECK_EQ(Version::V1, version); - v1.emplace(); - v1->client_eid = client_eid; - v1->authenticator_eid = authenticator_eid; - v1->session_pre_key = session_pre_key; -} - -CableDiscoveryData::CableDiscoveryData( - base::span<const uint8_t, kCableQRSecretSize> qr_secret, - base::span<const uint8_t, kCableIdentityKeySeedSize> identity_key_seed) { - InitFromQRSecret(qr_secret); - v2->local_identity_seed = fido_parsing_utils::Materialize(identity_key_seed); -} - -// static -base::Optional<CableDiscoveryData> CableDiscoveryData::FromQRData( - base::span<const uint8_t, - kCableCompressedPublicKeySize + kCableQRSecretSize> qr_data) { - auto qr_secret = qr_data.subspan(kCableCompressedPublicKeySize); - CableDiscoveryData discovery_data; - discovery_data.InitFromQRSecret(base::span<const uint8_t, kCableQRSecretSize>( - qr_secret.data(), qr_secret.size())); - - bssl::UniquePtr<EC_GROUP> p256( - EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); - bssl::UniquePtr<EC_POINT> point(EC_POINT_new(p256.get())); - if (!EC_POINT_oct2point(p256.get(), point.get(), qr_data.data(), - kCableCompressedPublicKeySize, /*ctx=*/nullptr)) { - return base::nullopt; - } - CableAuthenticatorIdentityKey& identity_key = - discovery_data.v2->peer_identity.emplace(); - CHECK_EQ(identity_key.size(), - EC_POINT_point2oct( - p256.get(), point.get(), POINT_CONVERSION_UNCOMPRESSED, - identity_key.data(), identity_key.size(), /*ctx=*/nullptr)); - - return discovery_data; -} - -CableDiscoveryData::CableDiscoveryData(const CableDiscoveryData& data) = - default; - -CableDiscoveryData& CableDiscoveryData::operator=( - const CableDiscoveryData& other) = default; - -CableDiscoveryData::~CableDiscoveryData() = default; - -bool CableDiscoveryData::operator==(const CableDiscoveryData& other) const { - if (version != other.version) { - return false; - } - - switch (version) { - case CableDiscoveryData::Version::V1: - return v1->client_eid == other.v1->client_eid && - v1->authenticator_eid == other.v1->authenticator_eid && - v1->session_pre_key == other.v1->session_pre_key; - - case CableDiscoveryData::Version::V2: - return v2->eid_gen_key == other.v2->eid_gen_key && - v2->psk_gen_key == other.v2->psk_gen_key && - v2->peer_identity == other.v2->peer_identity && - v2->peer_name == other.v2->peer_name; - - case CableDiscoveryData::Version::INVALID: - CHECK(false); - return false; - } -} - -base::Optional<CableNonce> CableDiscoveryData::Match( - const CableEidArray& eid) const { - switch (version) { - case Version::V1: { - if (eid != v1->authenticator_eid) { - return base::nullopt; - } - - // The nonce is the first eight bytes of the EID. - CableNonce nonce; - const bool ok = - fido_parsing_utils::ExtractArray(v1->client_eid, 0, &nonce); - DCHECK(ok); - return nonce; - } - - case Version::V2: { - // Attempt to decrypt the EID with the EID generator key and check whether - // it has a valid structure. - AES_KEY key; - CHECK(AES_set_decrypt_key(v2->eid_gen_key.data(), - /*bits=*/8 * v2->eid_gen_key.size(), - &key) == 0); - static_assert(kCableEphemeralIdSize == AES_BLOCK_SIZE, - "EIDs are not AES blocks"); - CableEidArray decrypted; - AES_decrypt(/*in=*/eid.data(), /*out=*/decrypted.data(), &key); - const uint8_t kZeroTrailer[8] = {0}; - static_assert(8 + sizeof(kZeroTrailer) == - std::tuple_size<decltype(decrypted)>::value, - "Trailer is wrong size"); - if (CRYPTO_memcmp(kZeroTrailer, decrypted.data() + 8, - sizeof(kZeroTrailer)) != 0) { - return base::nullopt; - } - - CableNonce nonce; - static_assert( - sizeof(nonce) <= std::tuple_size<decltype(decrypted)>::value, - "nonce too large"); - memcpy(nonce.data(), decrypted.data(), sizeof(nonce)); - return nonce; - } - - case Version::INVALID: - DCHECK(false); - return base::nullopt; - } -} - -// static -QRGeneratorKey CableDiscoveryData::NewQRKey() { - QRGeneratorKey key; - crypto::RandBytes(key.data(), key.size()); - return key; -} - -// static -int64_t CableDiscoveryData::CurrentTimeTick() { - // The ticks are currently 256ms. - return base::TimeTicks::Now().since_origin().InMilliseconds() >> 8; -} - -// static -std::array<uint8_t, kCableQRSecretSize> CableDiscoveryData::DeriveQRSecret( - base::span<const uint8_t, 32> qr_generator_key, - const int64_t tick) { - std::array<uint8_t, kCableQRSecretSize> ret; - DeriveQRValue(qr_generator_key, tick, QRValue::QR_SECRET, ret); - return ret; -} - -// static -CableIdentityKeySeed CableDiscoveryData::DeriveIdentityKeySeed( - base::span<const uint8_t, 32> qr_generator_key, - const int64_t tick) { - std::array<uint8_t, kCableIdentityKeySeedSize> ret; - DeriveQRValue(qr_generator_key, tick, QRValue::IDENTITY_KEY_SEED, ret); - return ret; -} - -// static -CableQRData CableDiscoveryData::DeriveQRData( - base::span<const uint8_t, 32> qr_generator_key, - const int64_t tick) { - auto identity_key_seed = DeriveIdentityKeySeed(qr_generator_key, tick); - bssl::UniquePtr<EC_GROUP> p256( - EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); - bssl::UniquePtr<EC_KEY> identity_key(EC_KEY_derive_from_secret( - p256.get(), identity_key_seed.data(), identity_key_seed.size())); - const EC_POINT* public_key = EC_KEY_get0_public_key(identity_key.get()); - CableQRData qr_data; - static_assert( - qr_data.size() == kCableCompressedPublicKeySize + kCableQRSecretSize, - "this code needs to be updated"); - CHECK_EQ(kCableCompressedPublicKeySize, - EC_POINT_point2oct(p256.get(), public_key, - POINT_CONVERSION_COMPRESSED, qr_data.data(), - kCableCompressedPublicKeySize, /*ctx=*/nullptr)); - - auto qr_secret = CableDiscoveryData::DeriveQRSecret(qr_generator_key, tick); - memcpy(&qr_data.data()[kCableCompressedPublicKeySize], qr_secret.data(), - qr_secret.size()); - - return qr_data; -} - -CableDiscoveryData::V2Data::V2Data() = default; -CableDiscoveryData::V2Data::V2Data(const V2Data&) = default; -CableDiscoveryData::V2Data::~V2Data() = default; - -void CableDiscoveryData::InitFromQRSecret( - base::span<const uint8_t, kCableQRSecretSize> qr_secret) { - version = Version::V2; - v2.emplace(); - - static const char kEIDGen[] = "caBLE QR to EID generator key"; - bool ok = - HKDF(v2->eid_gen_key.data(), v2->eid_gen_key.size(), EVP_sha256(), - qr_secret.data(), qr_secret.size(), /*salt=*/nullptr, 0, - reinterpret_cast<const uint8_t*>(kEIDGen), sizeof(kEIDGen) - 1); - DCHECK(ok); - - static const char kPSKGen[] = "caBLE QR to PSK generator key"; - ok = HKDF(v2->psk_gen_key.data(), v2->psk_gen_key.size(), EVP_sha256(), - qr_secret.data(), qr_secret.size(), /*salt=*/nullptr, 0, - reinterpret_cast<const uint8_t*>(kPSKGen), sizeof(kPSKGen) - 1); - DCHECK(ok); -} - // FidoCableDiscovery::CableV1DiscoveryEvent --------------------------------- // CableV1DiscoveryEvent enumerates several things that can occur during a caBLE @@ -352,12 +115,14 @@ enum class FidoCableDiscovery::CableV1DiscoveryEvent : int { kAdapterManuallyPowered = 4, kAdapterPoweredOff = 5, kScanningStarted = 6, + kStartScanningFailed = 12, + kScanningStoppedUnexpectedly = 13, kAdvertisementRegistered = 7, kFirstCableDeviceFound = 8, kFirstCableDeviceGATTConnected = 9, kFirstCableHandshakeSucceeded = 10, kFirstCableDeviceTimeout = 11, - kMaxValue = kFirstCableDeviceTimeout, + kMaxValue = kScanningStoppedUnexpectedly, }; // FidoCableDiscovery::Result ------------------------------------------------- @@ -493,10 +258,6 @@ void FidoCableDiscovery::OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter) { if (has_v1_discovery_data_) { RecordCableV1DiscoveryEventOnce(CableV1DiscoveryEvent::kAdapterPresent); - if (adapter->IsPowered()) { - RecordCableV1DiscoveryEventOnce( - CableV1DiscoveryEvent::kAdapterAlreadyPowered); - } } DCHECK(!adapter_); @@ -506,6 +267,10 @@ void FidoCableDiscovery::OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter) { adapter_->AddObserver(this); if (adapter_->IsPowered()) { + if (has_v1_discovery_data_) { + RecordCableV1DiscoveryEventOnce( + CableV1DiscoveryEvent::kAdapterAlreadyPowered); + } OnSetPowered(); } @@ -589,6 +354,22 @@ void FidoCableDiscovery::AdapterPoweredChanged(BluetoothAdapter* adapter, #endif // defined(OS_WIN) } +void FidoCableDiscovery::AdapterDiscoveringChanged(BluetoothAdapter* adapter, + bool is_scanning) { + FIDO_LOG(DEBUG) << "AdapterDiscoveringChanged() is_scanning=" << is_scanning; + + // Ignore updates while we're not scanning for caBLE devices ourselves. Other + // things in Chrome may start or stop scans at any time. + if (!discovery_session_) { + return; + } + + if (has_v1_discovery_data_ && !is_scanning) { + RecordCableV1DiscoveryEventOnce( + CableV1DiscoveryEvent::kScanningStoppedUnexpectedly); + } +} + void FidoCableDiscovery::FidoCableDeviceConnected(FidoCableDevice* device, bool success) { if (!success || !IsObservedV1Device(device->GetAddress())) { @@ -637,6 +418,10 @@ void FidoCableDiscovery::OnStartDiscoverySession( void FidoCableDiscovery::OnStartDiscoverySessionError() { FIDO_LOG(ERROR) << "Failed to start caBLE discovery"; + if (has_v1_discovery_data_) { + RecordCableV1DiscoveryEventOnce( + CableV1DiscoveryEvent::kStartScanningFailed); + } } void FidoCableDiscovery::StartAdvertisement() { diff --git a/chromium/device/fido/cable/fido_cable_discovery.h b/chromium/device/fido/cable/fido_cable_discovery.h index 286c28d72be..5381ff80477 100644 --- a/chromium/device/fido/cable/fido_cable_discovery.h +++ b/chromium/device/fido/cable/fido_cable_discovery.h @@ -149,6 +149,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDiscovery void DeviceRemoved(BluetoothAdapter* adapter, BluetoothDevice* device) override; void AdapterPoweredChanged(BluetoothAdapter* adapter, bool powered) override; + void AdapterDiscoveringChanged(BluetoothAdapter* adapter, + bool discovering) override; // FidoCableDevice::Observer: void FidoCableDeviceConnected(FidoCableDevice* device, bool success) override; @@ -158,10 +160,12 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDiscovery std::unique_ptr<BluetoothDiscoverySession> discovery_session_; std::vector<CableDiscoveryData> discovery_data_; + // active_authenticator_eids_ contains authenticator EIDs for which a // handshake is currently running. Further advertisements for the same EIDs // will be ignored. std::set<CableEidArray> active_authenticator_eids_; + // active_devices_ contains the BLE addresses of devices for which a handshake // is already running. Further advertisements from these devices will be // ignored. However, devices may rotate their BLE address at will so this is @@ -185,6 +189,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDiscovery // that the device-log isn't spammed. mutable base::flat_map<std::string, std::unique_ptr<ObservedDeviceData>> observed_devices_; + // noted_obsolete_eids_ remembers QR-code EIDs that have been logged as // valid-but-expired in order to avoid spamming the device-log. mutable base::flat_set<CableEidArray> noted_obsolete_eids_; diff --git a/chromium/device/fido/cable/fido_cable_discovery_unittest.cc b/chromium/device/fido/cable/fido_cable_discovery_unittest.cc index 167891d4003..020dc238c1a 100644 --- a/chromium/device/fido/cable/fido_cable_discovery_unittest.cc +++ b/chromium/device/fido/cable/fido_cable_discovery_unittest.cc @@ -153,11 +153,9 @@ class CableMockBluetoothAdvertisement : public BluetoothAdvertisement { void ExpectUnregisterAndSucceed() { EXPECT_CALL(*this, Unregister(_, _)) - .WillOnce( - ::testing::WithArg<0>(::testing::Invoke([](const auto& success_cb) { - base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, - success_cb); - }))); + .WillOnce(::testing::WithArg<0>([](const auto& success_cb) { + base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, success_cb); + })); } private: @@ -254,7 +252,7 @@ class CableMockAdapter : public MockBluetoothAdapter { advertisement = base::MakeRefCounted<CableMockBluetoothAdvertisement>(); EXPECT_CALL(*advertisement, Unregister(_, _)) .WillRepeatedly(::testing::WithArg<0>( - [](const auto& callback) { callback.Run(); })); + [](auto callback) { std::move(callback).Run(); })); } EXPECT_CALL(*this, diff --git a/chromium/device/fido/cable/fido_cable_handshake_handler.cc b/chromium/device/fido/cable/fido_cable_handshake_handler.cc index 07abf0c17bb..2a120e1ebe8 100644 --- a/chromium/device/fido/cable/fido_cable_handshake_handler.cc +++ b/chromium/device/fido/cable/fido_cable_handshake_handler.cc @@ -178,15 +178,12 @@ FidoCableV1HandshakeHandler::GetEncryptionKeyAfterSuccessfulHandshake( /*derived_key_length=*/32); } -// kP256PointSize is the number of bytes in an X9.62 encoding of a P-256 point. -static constexpr size_t kP256PointSize = 65; - FidoCableV2HandshakeHandler::FidoCableV2HandshakeHandler( FidoCableDevice* cable_device, base::span<const uint8_t, 32> psk_gen_key, base::span<const uint8_t, 8> nonce, base::span<const uint8_t, kCableEphemeralIdSize> eid, - base::Optional<base::span<const uint8_t, kP256PointSize>> peer_identity, + base::Optional<base::span<const uint8_t, kP256X962Length>> peer_identity, base::Optional<base::span<const uint8_t, kCableIdentityKeySeedSize>> local_seed, base::RepeatingCallback<void(std::unique_ptr<CableDiscoveryData>)> diff --git a/chromium/device/fido/cable/noise.cc b/chromium/device/fido/cable/noise.cc index b5526867f21..7926500005f 100644 --- a/chromium/device/fido/cable/noise.cc +++ b/chromium/device/fido/cable/noise.cc @@ -8,8 +8,11 @@ #include "crypto/aead.h" #include "crypto/sha2.h" +#include "device/fido/fido_constants.h" #include "third_party/boringssl/src/include/openssl/digest.h" +#include "third_party/boringssl/src/include/openssl/ec.h" #include "third_party/boringssl/src/include/openssl/hkdf.h" +#include "third_party/boringssl/src/include/openssl/obj.h" #include "third_party/boringssl/src/include/openssl/sha.h" namespace { @@ -121,6 +124,16 @@ base::Optional<std::vector<uint8_t>> Noise::DecryptAndHash( return plaintext; } +void Noise::MixHashPoint(const EC_POINT* point) { + uint8_t x962[kP256X962Length]; + bssl::UniquePtr<EC_GROUP> p256( + EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); + CHECK_EQ(sizeof(x962), + EC_POINT_point2oct(p256.get(), point, POINT_CONVERSION_UNCOMPRESSED, + x962, sizeof(x962), /*ctx=*/nullptr)); + MixHash(x962); +} + std::tuple<std::array<uint8_t, 32>, std::array<uint8_t, 32>> Noise::traffic_keys() const { return HKDF2(chaining_key_, base::span<const uint8_t>()); diff --git a/chromium/device/fido/cable/noise.h b/chromium/device/fido/cable/noise.h index 8bd4d8c906c..0372f452b82 100644 --- a/chromium/device/fido/cable/noise.h +++ b/chromium/device/fido/cable/noise.h @@ -12,6 +12,7 @@ #include "base/component_export.h" #include "base/containers/span.h" +#include "third_party/boringssl/src/include/openssl/base.h" namespace device { @@ -41,6 +42,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) Noise { base::Optional<std::vector<uint8_t>> DecryptAndHash( base::span<const uint8_t> ciphertext); + // MaxHashPoint calls |MixHash| with the uncompressed, X9.62 serialization of + // |point|. + void MixHashPoint(const EC_POINT* point); + // traffic_keys() calls Split from the protocol spec but, rather than // returning CipherState objects, returns the raw keys. std::tuple<std::array<uint8_t, 32>, std::array<uint8_t, 32>> traffic_keys() diff --git a/chromium/device/fido/cable/v2_handshake.cc b/chromium/device/fido/cable/v2_handshake.cc index 98351ac37cb..b9f85a20624 100644 --- a/chromium/device/fido/cable/v2_handshake.cc +++ b/chromium/device/fido/cable/v2_handshake.cc @@ -170,7 +170,7 @@ HandshakeInitiator::HandshakeInitiator( base::span<const uint8_t, 32> psk_gen_key, base::span<const uint8_t, 8> nonce, base::span<const uint8_t, kCableEphemeralIdSize> eid, - base::Optional<base::span<const uint8_t, kP256PointSize>> peer_identity, + base::Optional<base::span<const uint8_t, kP256X962Length>> peer_identity, base::Optional<base::span<const uint8_t, kCableIdentityKeySeedSize>> local_seed) : eid_(fido_parsing_utils::Materialize(eid)) { @@ -182,7 +182,10 @@ HandshakeInitiator::HandshakeInitiator( peer_identity_ = fido_parsing_utils::Materialize(*peer_identity); } if (local_seed) { - local_seed_ = fido_parsing_utils::Materialize(*local_seed); + bssl::UniquePtr<EC_GROUP> p256( + EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); + local_identity_.reset(EC_KEY_derive_from_secret( + p256.get(), local_seed->data(), local_seed->size())); } } @@ -192,16 +195,18 @@ std::vector<uint8_t> HandshakeInitiator::BuildInitialMessage() { if (peer_identity_) { noise_.Init(Noise::HandshakeType::kNKpsk0); noise_.MixHash(kPairedPrologue); + noise_.MixHash(*peer_identity_); } else { noise_.Init(Noise::HandshakeType::kKNpsk0); noise_.MixHash(kQRPrologue); + noise_.MixHashPoint(EC_KEY_get0_public_key(local_identity_.get())); } noise_.MixKeyAndHash(psk_); ephemeral_key_.reset(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); const EC_GROUP* group = EC_KEY_get0_group(ephemeral_key_.get()); CHECK(EC_KEY_generate_key(ephemeral_key_.get())); - uint8_t ephemeral_key_public_bytes[kP256PointSize]; + uint8_t ephemeral_key_public_bytes[kP256X962Length]; CHECK_EQ(sizeof(ephemeral_key_public_bytes), EC_POINT_point2oct( group, EC_KEY_get0_public_key(ephemeral_key_.get()), @@ -244,11 +249,11 @@ std::vector<uint8_t> HandshakeInitiator::BuildInitialMessage() { base::Optional<std::pair<std::unique_ptr<Crypter>, base::Optional<std::unique_ptr<CableDiscoveryData>>>> HandshakeInitiator::ProcessResponse(base::span<const uint8_t> response) { - if (response.size() < kP256PointSize) { + if (response.size() < kP256X962Length) { return base::nullopt; } - auto peer_point_bytes = response.subspan(0, kP256PointSize); - auto ciphertext = response.subspan(kP256PointSize); + auto peer_point_bytes = response.subspan(0, kP256X962Length); + auto ciphertext = response.subspan(kP256X962Length); bssl::UniquePtr<EC_POINT> peer_point( EC_POINT_new(EC_KEY_get0_group(ephemeral_key_.get()))); @@ -267,12 +272,10 @@ HandshakeInitiator::ProcessResponse(base::span<const uint8_t> response) { noise_.MixKey(peer_point_bytes); noise_.MixKey(shared_key_ee); - if (local_seed_) { + if (local_identity_) { uint8_t shared_key_se[32]; - bssl::UniquePtr<EC_KEY> identity_key(EC_KEY_derive_from_secret( - group, local_seed_->data(), local_seed_->size())); if (ECDH_compute_key(shared_key_se, sizeof(shared_key_se), peer_point.get(), - identity_key.get(), + local_identity_.get(), /*kdf=*/nullptr) != sizeof(shared_key_se)) { return base::nullopt; } @@ -354,7 +357,7 @@ base::Optional<std::unique_ptr<Crypter>> RespondToHandshake( base::span<const uint8_t> peer_point_bytes; base::span<const uint8_t> ciphertext; if (!CBS_get_span(&cbs, &eid, device::kCableEphemeralIdSize) || - !CBS_get_span(&cbs, &peer_point_bytes, kP256PointSize) || + !CBS_get_span(&cbs, &peer_point_bytes, kP256X962Length) || !CBS_get_span(&cbs, &ciphertext, 16) || CBS_len(&cbs) != 0) { return base::nullopt; } @@ -368,9 +371,11 @@ base::Optional<std::unique_ptr<Crypter>> RespondToHandshake( if (identity) { noise.Init(device::Noise::HandshakeType::kNKpsk0); noise.MixHash(kPairedPrologue); + noise.MixHashPoint(EC_KEY_get0_public_key(identity)); } else { noise.Init(device::Noise::HandshakeType::kKNpsk0); noise.MixHash(kQRPrologue); + noise.MixHashPoint(peer_identity); } std::array<uint8_t, 32> psk; @@ -410,7 +415,7 @@ base::Optional<std::unique_ptr<Crypter>> RespondToHandshake( return base::nullopt; } - uint8_t ephemeral_key_public_bytes[kP256PointSize]; + uint8_t ephemeral_key_public_bytes[kP256X962Length]; CHECK_EQ(sizeof(ephemeral_key_public_bytes), EC_POINT_point2oct( group, EC_KEY_get0_public_key(ephemeral_key.get()), diff --git a/chromium/device/fido/cable/v2_handshake.h b/chromium/device/fido/cable/v2_handshake.h index fdfdeb774d4..0b11f3a339d 100644 --- a/chromium/device/fido/cable/v2_handshake.h +++ b/chromium/device/fido/cable/v2_handshake.h @@ -104,7 +104,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) HandshakeInitiator { std::array<uint8_t, 32> psk_; base::Optional<std::array<uint8_t, kP256PointSize>> peer_identity_; - base::Optional<std::array<uint8_t, kCableIdentityKeySeedSize>> local_seed_; + bssl::UniquePtr<EC_KEY> local_identity_; bssl::UniquePtr<EC_KEY> ephemeral_key_; }; diff --git a/chromium/device/fido/cbor_extract.cc b/chromium/device/fido/cbor_extract.cc index ebbcc56ae60..ebde9b1d01f 100644 --- a/chromium/device/fido/cbor_extract.cc +++ b/chromium/device/fido/cbor_extract.cc @@ -64,8 +64,8 @@ class Extractor { step_i_ += key.size() + 1; map_it = map.find(cbor::Value(std::move(key))); } else { - map_it = map.find( - cbor::Value(static_cast<int64_t>(key_or_string_indicator))); + map_it = map.find(cbor::Value(static_cast<int64_t>( + static_cast<int8_t>(key_or_string_indicator)))); } const void** output = nullptr; diff --git a/chromium/device/fido/cbor_extract.h b/chromium/device/fido/cbor_extract.h index c24f8310533..5a99ac6d0c9 100644 --- a/chromium/device/fido/cbor_extract.h +++ b/chromium/device/fido/cbor_extract.h @@ -8,6 +8,7 @@ #include "base/callback_forward.h" #include "base/component_export.h" #include "base/containers/span.h" +#include "base/memory/checked_ptr.h" #include "components/cbor/values.h" namespace device { @@ -51,8 +52,8 @@ namespace cbor_extract { // Output values are also pointers into the input cbor::Value, so that cannot // be destroyed until processing is complete. // -// Keys for the element are either specified by IntKey<S>(x), where x < 255, or -// StringKey<S>() followed by a NUL-terminated string: +// Keys for the element are either specified by IntKey<S>(x), where -128 <= x < +// 127, or StringKey<S>() followed by a NUL-terminated string: // // static constexpr StepOrByte<MyObj> kSteps[] = { // ELEMENT(Is::kRequired, MyObj, value), @@ -124,7 +125,7 @@ template <typename S> struct StepOrByte { // STRING_KEY is the magic value of |u8| that indicates that this is not an // integer key, but the a NUL-terminated string follows. - static constexpr uint8_t STRING_KEY = 255; + static constexpr uint8_t STRING_KEY = 127; constexpr explicit StepOrByte(const internal::Step& in_step) : step(in_step) {} @@ -145,8 +146,10 @@ struct StepOrByte { offsetof(clas, member)) template <typename S> -constexpr StepOrByte<S> IntKey(unsigned key) { - if (key >= 256 || key == StepOrByte<S>::STRING_KEY) { +constexpr StepOrByte<S> IntKey(int key) { + if (key > std::numeric_limits<int8_t>::max() || + key < std::numeric_limits<int8_t>::min() || + key == StepOrByte<S>::STRING_KEY) { // It's a compile-time error if __builtin_unreachable is reachable. __builtin_unreachable(); } @@ -214,6 +217,14 @@ constexpr StepOrByte<S> Element(const Is required, } template <typename S> +constexpr StepOrByte<S> Element( + const Is required, + CheckedPtr<const std::vector<uint8_t>> S::*member, + uintptr_t offset) { + return ElementImpl<S>(required, offset, internal::Type::kBytestring); +} + +template <typename S> constexpr StepOrByte<S> Element(const Is required, const std::string* S::*member, uintptr_t offset) { @@ -222,6 +233,13 @@ constexpr StepOrByte<S> Element(const Is required, template <typename S> constexpr StepOrByte<S> Element(const Is required, + CheckedPtr<const std::string> S::*member, + uintptr_t offset) { + return ElementImpl<S>(required, offset, internal::Type::kString); +} + +template <typename S> +constexpr StepOrByte<S> Element(const Is required, const int64_t* S::*member, uintptr_t offset) { return ElementImpl<S>(required, offset, internal::Type::kInt); @@ -229,12 +247,27 @@ constexpr StepOrByte<S> Element(const Is required, template <typename S> constexpr StepOrByte<S> Element(const Is required, + CheckedPtr<const int64_t> S::*member, + uintptr_t offset) { + return ElementImpl<S>(required, offset, internal::Type::kInt); +} + +template <typename S> +constexpr StepOrByte<S> Element(const Is required, const std::vector<cbor::Value>* S::*member, uintptr_t offset) { return ElementImpl<S>(required, offset, internal::Type::kArray); } template <typename S> +constexpr StepOrByte<S> Element( + const Is required, + CheckedPtr<const std::vector<cbor::Value>> S::*member, + uintptr_t offset) { + return ElementImpl<S>(required, offset, internal::Type::kArray); +} + +template <typename S> constexpr StepOrByte<S> Element(const Is required, const cbor::Value* S::*member, uintptr_t offset) { @@ -243,11 +276,25 @@ constexpr StepOrByte<S> Element(const Is required, template <typename S> constexpr StepOrByte<S> Element(const Is required, + CheckedPtr<const cbor::Value> S::*member, + uintptr_t offset) { + return ElementImpl<S>(required, offset, internal::Type::kValue); +} + +template <typename S> +constexpr StepOrByte<S> Element(const Is required, const bool* S::*member, uintptr_t offset) { return ElementImpl<S>(required, offset, internal::Type::kBoolean); } +template <typename S> +constexpr StepOrByte<S> Element(const Is required, + CheckedPtr<const bool> S::*member, + uintptr_t offset) { + return ElementImpl<S>(required, offset, internal::Type::kBoolean); +} + COMPONENT_EXPORT(DEVICE_FIDO) bool Extract(base::span<const void*> outputs, base::span<const StepOrByte<void>> steps, diff --git a/chromium/device/fido/cbor_extract_unittest.cc b/chromium/device/fido/cbor_extract_unittest.cc index 714feca8195..a70eb5e1d83 100644 --- a/chromium/device/fido/cbor_extract_unittest.cc +++ b/chromium/device/fido/cbor_extract_unittest.cc @@ -36,7 +36,8 @@ struct MakeCredRequest { const std::vector<cbor::Value>* excluded_credentials; const bool* resident_key; const bool* user_verification; - const bool* u8_test; + const bool* large_test; + const bool* negative_test; }; TEST(CBORExtract, Basic) { @@ -78,7 +79,8 @@ TEST(CBORExtract, Basic) { make_cred.emplace(4, std::move(cred_params)); make_cred.emplace(5, std::move(excluded_creds)); make_cred.emplace(7, std::move(options)); - make_cred.emplace(0xf0, false); + make_cred.emplace(100, false); + make_cred.emplace(-3, true); static constexpr cbor_extract::StepOrByte<MakeCredRequest> kMakeCredParseSteps[] = { @@ -111,8 +113,11 @@ TEST(CBORExtract, Basic) { StringKey<MakeCredRequest>(), 'u', 'v', '\0', Stop<MakeCredRequest>(), - ELEMENT(Is::kRequired, MakeCredRequest, u8_test), - IntKey<MakeCredRequest>(0xf0), + ELEMENT(Is::kRequired, MakeCredRequest, large_test), + IntKey<MakeCredRequest>(100), + + ELEMENT(Is::kRequired, MakeCredRequest, negative_test), + IntKey<MakeCredRequest>(-3), Stop<MakeCredRequest>(), // clang-format on @@ -129,7 +134,8 @@ TEST(CBORExtract, Basic) { EXPECT_EQ(make_cred_request.excluded_credentials->size(), 3u); EXPECT_TRUE(*make_cred_request.resident_key); EXPECT_TRUE(make_cred_request.user_verification == nullptr); - EXPECT_FALSE(*make_cred_request.u8_test); + EXPECT_FALSE(*make_cred_request.large_test); + EXPECT_TRUE(*make_cred_request.negative_test); std::vector<int64_t> algs; EXPECT_TRUE(cbor_extract::ForEachPublicKeyEntry( diff --git a/chromium/device/fido/credential_management_handler.cc b/chromium/device/fido/credential_management_handler.cc index e1e0f2c57d5..8d8c6fa44bc 100644 --- a/chromium/device/fido/credential_management_handler.cc +++ b/chromium/device/fido/credential_management_handler.cc @@ -118,7 +118,8 @@ void CredentialManagementHandler::OnHavePIN(std::string pin) { state_ = State::kGettingPINToken; authenticator_->GetPINToken( - std::move(pin), + std::move(pin), {pin::Permissions::kCredentialManagement}, + /*rp_id=*/base::nullopt, base::BindOnce(&CredentialManagementHandler::OnHavePINToken, weak_factory_.GetWeakPtr())); } diff --git a/chromium/device/fido/ctap_response_unittest.cc b/chromium/device/fido/ctap_response_unittest.cc index 1a20bec1c3a..ffc487f46eb 100644 --- a/chromium/device/fido/ctap_response_unittest.cc +++ b/chromium/device/fido/ctap_response_unittest.cc @@ -15,6 +15,7 @@ #include "device/fido/fido_constants.h" #include "device/fido/fido_parsing_utils.h" #include "device/fido/fido_test_data.h" +#include "device/fido/fido_types.h" #include "device/fido/opaque_attestation_statement.h" #include "device/fido/p256_public_key.h" #include "device/fido/public_key.h" @@ -25,6 +26,7 @@ namespace device { namespace { +// clang-format off constexpr uint8_t kTestAuthenticatorGetInfoResponseWithNoVersion[] = { // Success status byte 0x00, @@ -143,6 +145,126 @@ constexpr uint8_t kTestAuthenticatorGetInfoResponseWithDuplicateVersion[] = { 0x81, 0x01, }; +constexpr uint8_t kTestAuthenticatorGetInfoResponseWithCtap2_1[] = { + // Success status byte + 0x00, + // Map of 6 elements + 0xA6, + // Key(01) - versions + 0x01, + // Array(03) + 0x83, + // "U2F_V2" + 0x66, 'U', '2', 'F', '_', 'V', '2', + // "FIDO_2_0" + 0x68, 'F', 'I', 'D', 'O', '_', '2', '_', '0', + // "FIDO_2_1" + 0x68, 'F', 'I', 'D', 'O', '_', '2', '_', '1', + // Key(02) - extensions + 0x02, + // Array(2) + 0x82, + // "uvm" + 0x63, 0x75, 0x76, 0x6D, + // "hmac-secret" + 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, + // Key(03) - AAGUID + 0x03, + // Bytes(16) + 0x50, 0xF8, 0xA0, 0x11, 0xF3, 0x8C, 0x0A, 0x4D, 0x15, 0x80, 0x06, 0x17, + 0x11, 0x1F, 0x9E, 0xDC, 0x7D, + // Key(04) - options + 0x04, + // Map(05) + 0xA5, + // Key - "rk" + 0x62, 0x72, 0x6B, + // true + 0xF5, + // Key - "up" + 0x62, 0x75, 0x70, + // true + 0xF5, + // Key - "uv" + 0x62, 0x75, 0x76, + // true + 0xF5, + // Key - "plat" + 0x64, 0x70, 0x6C, 0x61, 0x74, + // true + 0xF5, + // Key - "clientPin" + 0x69, 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, + // false + 0xF4, + // Key(05) - Max message size + 0x05, + // 1200 + 0x19, 0x04, 0xB0, + // Key(06) - Pin protocols + 0x06, + // Array[1] + 0x81, 0x01, +}; + +constexpr uint8_t kTestAuthenticatorGetInfoResponseOnlyCtap2_1[] = { + // Success status byte + 0x00, + // Map of 6 elements + 0xA6, + // Key(01) - versions + 0x01, + // Array(01) + 0x81, + // "FIDO_2_1" + 0x68, 'F', 'I', 'D', 'O', '_', '2', '_', '1', + // Key(02) - extensions + 0x02, + // Array(2) + 0x82, + // "uvm" + 0x63, 0x75, 0x76, 0x6D, + // "hmac-secret" + 0x6B, 0x68, 0x6D, 0x61, 0x63, 0x2D, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, + // Key(03) - AAGUID + 0x03, + // Bytes(16) + 0x50, 0xF8, 0xA0, 0x11, 0xF3, 0x8C, 0x0A, 0x4D, 0x15, 0x80, 0x06, 0x17, + 0x11, 0x1F, 0x9E, 0xDC, 0x7D, + // Key(04) - options + 0x04, + // Map(05) + 0xA5, + // Key - "rk" + 0x62, 0x72, 0x6B, + // true + 0xF5, + // Key - "up" + 0x62, 0x75, 0x70, + // true + 0xF5, + // Key - "uv" + 0x62, 0x75, 0x76, + // true + 0xF5, + // Key - "plat" + 0x64, 0x70, 0x6C, 0x61, 0x74, + // true + 0xF5, + // Key - "clientPin" + 0x69, 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x50, 0x69, 0x6E, + // false + 0xF4, + // Key(05) - Max message size + 0x05, + // 1200 + 0x19, 0x04, 0xB0, + // Key(06) - Pin protocols + 0x06, + // Array[1] + 0x81, 0x01, +}; + constexpr uint8_t kTestAuthenticatorGetInfoResponseWithIncorrectAaguid[] = { // Success status byte 0x00, @@ -261,6 +383,7 @@ constexpr uint8_t kAuthDataCBOR[] = { // kTestAuthenticatorDataPrefix|, |kTestAttestedCredentialDataPrefix|, // and test_data::kTestECPublicKeyCOSE. 0x58, 0xC4}; +// clang-format on constexpr std::array<uint8_t, kAaguidLength> kTestDeviceAaguid = { {0xF8, 0xA0, 0x11, 0xF3, 0x8C, 0x0A, 0x4D, 0x15, 0x80, 0x06, 0x17, 0x11, @@ -426,10 +549,10 @@ TEST(CTAPResponseTest, TestParseRegisterResponseData) { // Test that an EC public key serializes to CBOR properly. TEST(CTAPResponseTest, TestSerializedPublicKey) { auto public_key = P256PublicKey::ExtractFromU2fRegistrationResponse( - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256), + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), test_data::kTestU2fRegisterResponse); ASSERT_TRUE(public_key); - EXPECT_THAT(public_key->cose_key_bytes(), + EXPECT_THAT(public_key->cose_key_bytes, ::testing::ElementsAreArray(test_data::kTestECPublicKeyCOSE)); } @@ -448,7 +571,7 @@ TEST(CTAPResponseTest, TestParseU2fAttestationStatementCBOR) { // Tests that well-formed attested credential data serializes properly. TEST(CTAPResponseTest, TestSerializeAttestedCredentialData) { auto public_key = P256PublicKey::ExtractFromU2fRegistrationResponse( - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256), + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), test_data::kTestU2fRegisterResponse); auto attested_data = AttestedCredentialData::CreateFromU2fRegisterResponse( test_data::kTestU2fRegisterResponse, std::move(public_key)); @@ -460,7 +583,7 @@ TEST(CTAPResponseTest, TestSerializeAttestedCredentialData) { // Tests that well-formed authenticator data serializes properly. TEST(CTAPResponseTest, TestSerializeAuthenticatorData) { auto public_key = P256PublicKey::ExtractFromU2fRegistrationResponse( - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256), + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), test_data::kTestU2fRegisterResponse); auto attested_data = AttestedCredentialData::CreateFromU2fRegisterResponse( test_data::kTestU2fRegisterResponse, std::move(public_key)); @@ -480,7 +603,7 @@ TEST(CTAPResponseTest, TestSerializeAuthenticatorData) { // Tests that a U2F attestation object serializes properly. TEST(CTAPResponseTest, TestSerializeU2fAttestationObject) { auto public_key = P256PublicKey::ExtractFromU2fRegistrationResponse( - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256), + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), test_data::kTestU2fRegisterResponse); auto attested_data = AttestedCredentialData::CreateFromU2fRegisterResponse( test_data::kTestU2fRegisterResponse, std::move(public_key)); @@ -587,6 +710,9 @@ TEST(CTAPResponseTest, TestReadGetInfoResponse) { base::Contains(get_info_response->versions, ProtocolVersion::kCtap2)); EXPECT_TRUE( base::Contains(get_info_response->versions, ProtocolVersion::kU2f)); + EXPECT_EQ(get_info_response->ctap2_versions.size(), 1u); + EXPECT_TRUE(base::Contains(get_info_response->ctap2_versions, + Ctap2Version::kCtap2_0)); EXPECT_TRUE(get_info_response->options.is_platform_device); EXPECT_TRUE(get_info_response->options.supports_resident_key); EXPECT_TRUE(get_info_response->options.supports_user_presence); @@ -618,6 +744,31 @@ TEST(CTAPResponseTest, TestReadGetInfoResponseWithDuplicateVersion) { ASSERT_TRUE(response); EXPECT_EQ(1u, response->versions.size()); EXPECT_TRUE(response->versions.contains(ProtocolVersion::kU2f)); + EXPECT_EQ(response->ctap2_versions.size(), 0u); +} + +TEST(CTAPResponseTest, TestReadGetInfoResponseWithCtap2_1) { + auto response = + ReadCTAPGetInfoResponse(kTestAuthenticatorGetInfoResponseWithCtap2_1); + ASSERT_TRUE(response); + EXPECT_EQ(2u, response->versions.size()); + EXPECT_TRUE(response->versions.contains(ProtocolVersion::kU2f)); + EXPECT_TRUE(response->versions.contains(ProtocolVersion::kCtap2)); + EXPECT_EQ(response->ctap2_versions.size(), 2u); + EXPECT_TRUE(base::Contains(response->ctap2_versions, Ctap2Version::kCtap2_0)); + EXPECT_TRUE(base::Contains(response->ctap2_versions, Ctap2Version::kCtap2_1)); +} + +// Tests that an authenticator returning only the string "FIDO_2_1" is properly +// recognized as a CTAP 2.1 authenticator. +TEST(CTAPResponseTest, TestReadGetInfoResponseOnlyCtap2_1) { + auto response = + ReadCTAPGetInfoResponse(kTestAuthenticatorGetInfoResponseOnlyCtap2_1); + ASSERT_TRUE(response); + EXPECT_EQ(1u, response->versions.size()); + EXPECT_TRUE(response->versions.contains(ProtocolVersion::kCtap2)); + EXPECT_EQ(response->ctap2_versions.size(), 1u); + EXPECT_TRUE(base::Contains(response->ctap2_versions, Ctap2Version::kCtap2_1)); } TEST(CTAPResponseTest, TestReadGetInfoResponseWithIncorrectFormat) { @@ -629,7 +780,8 @@ TEST(CTAPResponseTest, TestReadGetInfoResponseWithIncorrectFormat) { TEST(CTAPResponseTest, TestSerializeGetInfoResponse) { AuthenticatorGetInfoResponse response( - {ProtocolVersion::kCtap2, ProtocolVersion::kU2f}, kTestDeviceAaguid); + {ProtocolVersion::kCtap2, ProtocolVersion::kU2f}, + {Ctap2Version::kCtap2_0}, kTestDeviceAaguid); response.extensions.emplace({std::string("uvm"), std::string("hmac-secret")}); AuthenticatorSupportedOptions options; options.supports_resident_key = true; @@ -691,7 +843,7 @@ TEST(CTAPResponseTest, TestSerializeMakeCredentialResponse) { fido_parsing_utils::Materialize( test_data::kCtap2MakeCredentialCredentialId), std::make_unique<PublicKey>( - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256), + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), kCoseEncodedPublicKey, base::nullopt)); AuthenticatorData authenticator_data(application_parameter, flag, signature_counter, diff --git a/chromium/device/fido/device_response_converter.cc b/chromium/device/fido/device_response_converter.cc index a394c21f96b..5bbf8c4ea98 100644 --- a/chromium/device/fido/device_response_converter.cc +++ b/chromium/device/fido/device_response_converter.cc @@ -32,7 +32,7 @@ namespace { constexpr size_t kResponseCodeLength = 1; ProtocolVersion ConvertStringToProtocolVersion(base::StringPiece version) { - if (version == kCtap2Version) + if (version == kCtap2Version || version == kCtap2_1Version) return ProtocolVersion::kCtap2; if (version == kU2fVersion) return ProtocolVersion::kU2f; @@ -40,6 +40,15 @@ ProtocolVersion ConvertStringToProtocolVersion(base::StringPiece version) { return ProtocolVersion::kUnknown; } +Ctap2Version ConvertStringToCtap2Version(base::StringPiece version) { + if (version == kCtap2Version) + return Ctap2Version::kCtap2_0; + if (version == kCtap2_1Version) + return Ctap2Version::kCtap2_1; + + return Ctap2Version::kUnknown; +} + // Converts a CBOR unsigned integer value to a uint32_t. The conversion is // clamped at uint32_max. uint32_t CBORUnsignedToUint32Safe(const cbor::Value& value) { @@ -195,6 +204,7 @@ base::Optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse( } base::flat_set<ProtocolVersion> protocol_versions; + base::flat_set<Ctap2Version> ctap2_versions; base::flat_set<base::StringPiece> advertised_protocols; for (const auto& version : it->second.GetArray()) { if (!version.is_string()) @@ -212,12 +222,11 @@ base::Optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse( continue; } - if (!protocol_versions.insert(protocol).second) { - // A duplicate value will have already caused an error therefore hitting - // this suggests that |ConvertStringToProtocolVersion| is non-injective. - NOTREACHED(); - return base::nullopt; + if (protocol == ProtocolVersion::kCtap2) { + ctap2_versions.insert(ConvertStringToCtap2Version(version_string)); } + + protocol_versions.insert(protocol); } if (protocol_versions.empty()) @@ -230,7 +239,7 @@ base::Optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse( } AuthenticatorGetInfoResponse response( - std::move(protocol_versions), + std::move(protocol_versions), ctap2_versions, base::make_span<kAaguidLength>(it->second.GetBytestring())); AuthenticatorSupportedOptions options; @@ -363,7 +372,7 @@ base::Optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse( if (!option_map_it->second.is_bool()) { return base::nullopt; } - options.supports_uv_token = option_map_it->second.GetBool(); + options.supports_pin_uv_auth_token = option_map_it->second.GetBool(); } option_map_it = option_map.find(CBOR(kDefaultCredProtectKey)); @@ -422,6 +431,47 @@ base::Optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse( response.max_credential_id_length = CBORUnsignedToUint32Safe(it->second); } + it = response_map.find(CBOR(10)); + if (it != response_map.end()) { + if (!it->second.is_array()) { + return base::nullopt; + } + + response.algorithms.clear(); + + const std::vector<cbor::Value>& algorithms = it->second.GetArray(); + for (const auto& algorithm : algorithms) { + // Entries are PublicKeyCredentialParameters + // https://w3c.github.io/webauthn/#dictdef-publickeycredentialparameters + if (!algorithm.is_map()) { + return base::nullopt; + } + + const auto& map = algorithm.GetMap(); + const auto type_it = map.find(CBOR("type")); + if (type_it == map.end() || !type_it->second.is_string()) { + return base::nullopt; + } + + if (type_it->second.GetString() != "public-key") { + continue; + } + + const auto alg_it = map.find(CBOR("alg")); + if (alg_it == map.end() || !alg_it->second.is_integer()) { + return base::nullopt; + } + + const int64_t alg = alg_it->second.GetInteger(); + if (alg < std::numeric_limits<int32_t>::min() || + alg > std::numeric_limits<int32_t>::max()) { + continue; + } + + response.algorithms.push_back(alg); + } + } + return base::Optional<AuthenticatorGetInfoResponse>(std::move(response)); } diff --git a/chromium/device/fido/ed25519_public_key.cc b/chromium/device/fido/ed25519_public_key.cc new file mode 100644 index 00000000000..ff86b9e7c8f --- /dev/null +++ b/chromium/device/fido/ed25519_public_key.cc @@ -0,0 +1,89 @@ +// Copyright 2020 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/ed25519_public_key.h" + +#include <utility> + +#include "components/cbor/writer.h" +#include "device/fido/cbor_extract.h" +#include "device/fido/fido_constants.h" +#include "third_party/boringssl/src/include/openssl/bn.h" +#include "third_party/boringssl/src/include/openssl/bytestring.h" +#include "third_party/boringssl/src/include/openssl/evp.h" +#include "third_party/boringssl/src/include/openssl/mem.h" +#include "third_party/boringssl/src/include/openssl/obj.h" +#include "third_party/boringssl/src/include/openssl/rsa.h" + +using device::cbor_extract::IntKey; +using device::cbor_extract::Is; +using device::cbor_extract::StepOrByte; +using device::cbor_extract::Stop; + +namespace device { + +// static +std::unique_ptr<PublicKey> Ed25519PublicKey::ExtractFromCOSEKey( + int32_t algorithm, + base::span<const uint8_t> cbor_bytes, + const cbor::Value::MapValue& map) { + // See https://tools.ietf.org/html/rfc8152#section-13.2 + struct COSEKey { + const int64_t* kty; + const int64_t* crv; + const std::vector<uint8_t>* key; + } cose_key; + + static constexpr cbor_extract::StepOrByte<COSEKey> kSteps[] = { + // clang-format off + ELEMENT(Is::kRequired, COSEKey, kty), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kKty)), + + ELEMENT(Is::kRequired, COSEKey, crv), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kEllipticCurve)), + + ELEMENT(Is::kRequired, COSEKey, key), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kEllipticX)), + + Stop<COSEKey>(), + // clang-format on + }; + + if (!cbor_extract::Extract<COSEKey>(&cose_key, kSteps, map) || + *cose_key.kty != static_cast<int64_t>(CoseKeyTypes::kOKP) || + *cose_key.crv != static_cast<int64_t>(CoseCurves::kEd25519) || + cose_key.key->size() != 32) { + return nullptr; + } + + // The COSE RFC says that "This contains the x-coordinate for the EC point". + // The RFC authors do not appear to understand what's going on because it + // actually just contains the Ed25519 public key, which you would expect, and + // which also encodes the y-coordinate as a sign bit. + // + // We could attempt to check whether |key| contains a quadratic residue, as it + // should. But that would involve diving into the guts of Ed25519 too much. + + bssl::UniquePtr<EVP_PKEY> pkey( + EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, /*engine=*/nullptr, + cose_key.key->data(), cose_key.key->size())); + if (!pkey) { + return nullptr; + } + + bssl::ScopedCBB cbb; + uint8_t* der_bytes = nullptr; + size_t der_bytes_len = 0; + CHECK(CBB_init(cbb.get(), /* initial size */ 128) && + EVP_marshal_public_key(cbb.get(), pkey.get()) && + CBB_finish(cbb.get(), &der_bytes, &der_bytes_len)); + + std::vector<uint8_t> der_bytes_vec(der_bytes, der_bytes + der_bytes_len); + OPENSSL_free(der_bytes); + + return std::make_unique<PublicKey>(algorithm, cbor_bytes, + std::move(der_bytes_vec)); +} + +} // namespace device diff --git a/chromium/device/fido/ed25519_public_key.h b/chromium/device/fido/ed25519_public_key.h new file mode 100644 index 00000000000..20ad4115d3e --- /dev/null +++ b/chromium/device/fido/ed25519_public_key.h @@ -0,0 +1,28 @@ +// Copyright 2020 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_ED25519_PUBLIC_KEY_H_ +#define DEVICE_FIDO_ED25519_PUBLIC_KEY_H_ + +#include <stdint.h> +#include <memory> + +#include "base/component_export.h" +#include "base/containers/span.h" +#include "components/cbor/values.h" +#include "device/fido/public_key.h" + +namespace device { + +class COMPONENT_EXPORT(DEVICE_FIDO) Ed25519PublicKey { + public: + static std::unique_ptr<PublicKey> ExtractFromCOSEKey( + int32_t algorithm, + base::span<const uint8_t> cbor_bytes, + const cbor::Value::MapValue& map); +}; + +} // namespace device + +#endif // DEVICE_FIDO_ED25519_PUBLIC_KEY_H_ diff --git a/chromium/device/fido/fido_authenticator.cc b/chromium/device/fido/fido_authenticator.cc index 1555716ef4b..72a09dbfe9b 100644 --- a/chromium/device/fido/fido_authenticator.cc +++ b/chromium/device/fido/fido_authenticator.cc @@ -26,6 +26,8 @@ void FidoAuthenticator::GetPinRetries( void FidoAuthenticator::GetPINToken( std::string pin, + const std::vector<pin::Permissions>& permissions, + base::Optional<std::string> rp_id, FidoAuthenticator::GetTokenCallback callback) { NOTREACHED(); } @@ -35,7 +37,12 @@ void FidoAuthenticator::GetUvRetries( NOTREACHED(); } +bool FidoAuthenticator::CanGetUvToken() { + return false; +} + void FidoAuthenticator::GetUvToken( + base::Optional<std::string> rp_id, FidoAuthenticator::GetTokenCallback callback) { NOTREACHED(); } @@ -121,6 +128,10 @@ void FidoAuthenticator::BioEnrollDelete(const pin::TokenResponse&, NOTREACHED(); } +base::Optional<base::span<const int32_t>> FidoAuthenticator::GetAlgorithms() { + return base::nullopt; +} + void FidoAuthenticator::Reset(ResetCallback callback) { std::move(callback).Run(CtapDeviceResponseCode::kCtap1ErrInvalidCommand, base::nullopt); @@ -134,4 +145,8 @@ bool FidoAuthenticator::SupportsCredProtectExtension() const { return Options() && Options()->supports_cred_protect; } +bool FidoAuthenticator::SupportsHMACSecretExtension() const { + return false; +} + } // namespace device diff --git a/chromium/device/fido/fido_authenticator.h b/chromium/device/fido/fido_authenticator.h index dde7bc6b4aa..a26ef40485f 100644 --- a/chromium/device/fido/fido_authenticator.h +++ b/chromium/device/fido/fido_authenticator.h @@ -9,6 +9,7 @@ #include "base/callback_forward.h" #include "base/component_export.h" +#include "base/containers/span.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "base/optional.h" @@ -96,11 +97,23 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoAuthenticator { // GetPINToken uses the given PIN to request a PinUvAuthToken from an // authenticator. It is only valid to call this method if |Options| indicates // that the authenticator supports PINs. - virtual void GetPINToken(std::string pin, GetTokenCallback callback); + // |permissions| are flags indicating which commands the token may be used + // for. + // |rp_id| binds the token to operations related to a given RP ID. |rp_id| + // must be set if |permissions| includes MakeCredential or GetAssertion. + virtual void GetPINToken(std::string pin, + const std::vector<pin::Permissions>& permissions, + base::Optional<std::string> rp_id, + GetTokenCallback callback); + // Returns |true| if the authenticator supports GetUvToken. + virtual bool CanGetUvToken(); // GetUvToken uses internal user verification to request a PinUvAuthToken from - // an authenticator. It is only valid to call this method if |Options| - // indicates that the authenticator supports UV tokens. - virtual void GetUvToken(GetTokenCallback callback); + // an authenticator. It is only valid to call this method if CanGetUvToken() + // returns true. + // |rp_id| must be set if the PinUvAuthToken will be used for MakeCredential + // or GetAssertion. + virtual void GetUvToken(base::Optional<std::string> rp_id, + GetTokenCallback callback); // SetPIN sets a new PIN on a device that does not currently have one. The // length of |pin| must respect |pin::kMinLength| and |pin::kMaxLength|. It is // only valid to call this method if |Options| indicates that the @@ -187,6 +200,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoAuthenticator { std::vector<uint8_t> template_id, BioEnrollmentCallback); + // GetAlgorithms returns the list of supported COSEAlgorithmIdentifiers, or + // |nullopt| if this is unknown and thus all requests should be tried in case + // they work. + virtual base::Optional<base::span<const int32_t>> GetAlgorithms(); + // Reset triggers a reset operation on the authenticator. This erases all // stored resident keys and any configured PIN. virtual void Reset(ResetCallback callback); @@ -195,6 +213,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoAuthenticator { virtual base::string16 GetDisplayName() const = 0; virtual ProtocolVersion SupportedProtocol() const; virtual bool SupportsCredProtectExtension() const; + virtual bool SupportsHMACSecretExtension() const; virtual const base::Optional<AuthenticatorSupportedOptions>& Options() const = 0; virtual base::Optional<FidoTransportProtocol> AuthenticatorTransport() diff --git a/chromium/device/fido/fido_constants.cc b/chromium/device/fido/fido_constants.cc index ceffcf68f7f..219cfa7c9fc 100644 --- a/chromium/device/fido/fido_constants.cc +++ b/chromium/device/fido/fido_constants.cc @@ -65,6 +65,7 @@ const char kCableClientHelloMessage[] = "caBLE v1 client hello"; const char kCtap2Version[] = "FIDO_2_0"; const char kU2fVersion[] = "U2F_V2"; +const char kCtap2_1Version[] = "FIDO_2_1"; const char kExtensionHmacSecret[] = "hmac-secret"; const char kExtensionCredProtect[] = "credProtect"; diff --git a/chromium/device/fido/fido_constants.h b/chromium/device/fido/fido_constants.h index 144d1fb6df7..ef44add20cd 100644 --- a/chromium/device/fido/fido_constants.h +++ b/chromium/device/fido/fido_constants.h @@ -63,6 +63,9 @@ constexpr size_t kAaguidLength = 16; // integer: https://www.w3.org/TR/webauthn/#sec-attested-credential-data constexpr size_t kCredentialIdLengthLength = 2; +// Length of an X9.62-encoded, uncompresed, P-256 public key. +constexpr size_t kP256X962Length = 1 /* type byte */ + 32 /* x */ + 32 /* y */; + // CTAP protocol device response code, as specified in // https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#authenticator-api enum class CtapDeviceResponseCode : uint8_t { @@ -245,17 +248,28 @@ enum class CoseKeyKey : int { // Enumerates COSE key types. See // https://tools.ietf.org/html/rfc8152#section-13 enum class CoseKeyTypes : int { + kOKP = 1, kEC2 = 2, kRSA = 3, + // kInvalidForTesting is a random 32-bit number used to test unknown key + // types. + kInvalidForTesting = 146919568, }; // Enumerates COSE elliptic curves. See // https://tools.ietf.org/html/rfc8152#section-13.1 -enum class CoseCurves : int { kP256 = 1 }; +enum class CoseCurves : int { + kP256 = 1, + kEd25519 = 6, +}; enum class CoseAlgorithmIdentifier : int { - kCoseEs256 = -7, - kCoseRs256 = -257, + kEs256 = -7, + kEdDSA = -8, + kRs256 = -257, + // kInvalidForTesting is a random 32-bit number used to test unknown + // algorithms. + kInvalidForTesting = 146919568, }; // APDU instruction code for U2F request encoding. @@ -371,10 +385,21 @@ COMPONENT_EXPORT(DEVICE_FIDO) extern const char kCableAuthenticatorHelloMessage[]; COMPONENT_EXPORT(DEVICE_FIDO) extern const char kCableClientHelloMessage[]; -// TODO(hongjunchoi): Add url to the official spec once it's standardized. +enum class Ctap2Version { + kUnknown = 0, + kCtap2_0 = 1, + kCtap2_1 = 2, +}; + +// Protocol version strings. +// https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetInfo COMPONENT_EXPORT(DEVICE_FIDO) extern const char kCtap2Version[]; COMPONENT_EXPORT(DEVICE_FIDO) extern const char kU2fVersion[]; +// The version identifier for CTAP 2.1. +// TODO(nsatragno): link to the spec once this is standardized. +COMPONENT_EXPORT(DEVICE_FIDO) extern const char kCtap2_1Version[]; + COMPONENT_EXPORT(DEVICE_FIDO) extern const char kExtensionHmacSecret[]; COMPONENT_EXPORT(DEVICE_FIDO) extern const char kExtensionCredProtect[]; COMPONENT_EXPORT(DEVICE_FIDO) diff --git a/chromium/device/fido/fido_device_authenticator.cc b/chromium/device/fido/fido_device_authenticator.cc index e55a322c4f3..ee1033ef93f 100644 --- a/chromium/device/fido/fido_device_authenticator.cc +++ b/chromium/device/fido/fido_device_authenticator.cc @@ -4,6 +4,7 @@ #include "device/fido/fido_device_authenticator.h" +#include <numeric> #include <utility> #include "base/bind.h" @@ -20,9 +21,46 @@ #include "device/fido/get_assertion_task.h" #include "device/fido/make_credential_task.h" #include "device/fido/pin.h" +#include "device/fido/u2f_command_constructor.h" namespace device { +namespace { + +// Helper method for determining correct bio enrollment version. +BioEnrollmentRequest::Version GetBioEnrollmentRequestVersion( + const AuthenticatorSupportedOptions& options) { + DCHECK(options.bio_enrollment_availability_preview != + AuthenticatorSupportedOptions::BioEnrollmentAvailability:: + kNotSupported || + options.bio_enrollment_availability != + AuthenticatorSupportedOptions::BioEnrollmentAvailability:: + kNotSupported); + return options.bio_enrollment_availability != + AuthenticatorSupportedOptions::BioEnrollmentAvailability:: + kNotSupported + ? BioEnrollmentRequest::kDefault + : BioEnrollmentRequest::kPreview; +} + +CredentialManagementRequest::Version GetCredentialManagementRequestVersion( + const AuthenticatorSupportedOptions& options) { + DCHECK(options.supports_credential_management_preview || + options.supports_credential_management); + return options.supports_credential_management + ? CredentialManagementRequest::kDefault + : CredentialManagementRequest::kPreview; +} + +uint8_t PermissionsToByte(const std::vector<pin::Permissions>& permissions) { + return std::accumulate(permissions.begin(), permissions.end(), 0, + [](uint8_t byte, pin::Permissions flag) { + return byte |= static_cast<uint8_t>(flag); + }); +} + +} // namespace + FidoDeviceAuthenticator::FidoDeviceAuthenticator( std::unique_ptr<FidoDevice> device) : device_(std::move(device)) {} @@ -59,12 +97,38 @@ void FidoDeviceAuthenticator::InitializeAuthenticatorDone( void FidoDeviceAuthenticator::MakeCredential(CtapMakeCredentialRequest request, MakeCredentialCallback callback) { - RunTask<MakeCredentialTask>(std::move(request), std::move(callback)); + // If the authenticator has UV configured then UV will be required in + // order to create a credential (as specified by CTAP 2.0), even if + // user-verification is "discouraged". However, if the request is U2F-only + // then that doesn't apply and UV must be set to discouraged so that the + // request can be translated to U2F. + if (!request.pin_auth && + options_->user_verification_availability == + AuthenticatorSupportedOptions::UserVerificationAvailability:: + kSupportedAndConfigured && + !request.is_u2f_only) { + request.user_verification = UserVerificationRequirement::kRequired; + } else { + request.user_verification = UserVerificationRequirement::kDiscouraged; + } + + RunTask<MakeCredentialTask, AuthenticatorMakeCredentialResponse, + CtapMakeCredentialRequest>(std::move(request), std::move(callback)); } void FidoDeviceAuthenticator::GetAssertion(CtapGetAssertionRequest request, GetAssertionCallback callback) { - RunTask<GetAssertionTask>(std::move(request), std::move(callback)); + if (!request.pin_auth && + options_->user_verification_availability == + AuthenticatorSupportedOptions::UserVerificationAvailability:: + kSupportedAndConfigured && + request.user_verification != UserVerificationRequirement::kDiscouraged) { + request.user_verification = UserVerificationRequirement::kRequired; + } else { + request.user_verification = UserVerificationRequirement::kDiscouraged; + } + RunTask<GetAssertionTask, AuthenticatorGetAssertionResponse, + CtapGetAssertionRequest>(std::move(request), std::move(callback)); } void FidoDeviceAuthenticator::GetNextAssertion(GetAssertionCallback callback) { @@ -109,30 +173,62 @@ void FidoDeviceAuthenticator::GetPinRetries(GetRetriesCallback callback) { void FidoDeviceAuthenticator::GetEphemeralKey( GetEphemeralKeyCallback callback) { + if (cached_ephemeral_key_.has_value()) { + std::move(callback).Run(CtapDeviceResponseCode::kSuccess, + cached_ephemeral_key_); + return; + } + DCHECK(Options()); DCHECK( Options()->client_pin_availability != AuthenticatorSupportedOptions::ClientPinAvailability::kNotSupported || - Options()->supports_uv_token); + Options()->supports_pin_uv_auth_token); RunOperation<pin::KeyAgreementRequest, pin::KeyAgreementResponse>( - pin::KeyAgreementRequest(), std::move(callback), + pin::KeyAgreementRequest(), + base::BindOnce(&FidoDeviceAuthenticator::OnHaveEphemeralKey, + weak_factory_.GetWeakPtr(), std::move(callback)), base::BindOnce(&pin::KeyAgreementResponse::Parse)); } -void FidoDeviceAuthenticator::GetPINToken(std::string pin, - GetTokenCallback callback) { +void FidoDeviceAuthenticator::OnHaveEphemeralKey( + GetEphemeralKeyCallback callback, + CtapDeviceResponseCode status, + base::Optional<pin::KeyAgreementResponse> key) { + if (status != CtapDeviceResponseCode::kSuccess) { + std::move(callback).Run(status, base::nullopt); + } + DCHECK(key.has_value()); + + cached_ephemeral_key_.emplace(std::move(key.value())); + std::move(callback).Run(CtapDeviceResponseCode::kSuccess, + cached_ephemeral_key_); +} + +void FidoDeviceAuthenticator::GetPINToken( + std::string pin, + const std::vector<pin::Permissions>& permissions, + base::Optional<std::string> rp_id, + GetTokenCallback callback) { DCHECK(Options()); DCHECK(Options()->client_pin_availability != AuthenticatorSupportedOptions::ClientPinAvailability::kNotSupported); + DCHECK_NE(permissions.size(), 0u); + DCHECK(!((base::Contains(permissions, pin::Permissions::kMakeCredential)) || + base::Contains(permissions, pin::Permissions::kGetAssertion)) || + rp_id); GetEphemeralKey(base::BindOnce( &FidoDeviceAuthenticator::OnHaveEphemeralKeyForGetPINToken, - weak_factory_.GetWeakPtr(), std::move(pin), std::move(callback))); + weak_factory_.GetWeakPtr(), std::move(pin), + PermissionsToByte(permissions), std::move(rp_id), std::move(callback))); } void FidoDeviceAuthenticator::OnHaveEphemeralKeyForGetPINToken( std::string pin, + uint8_t permissions, + base::Optional<std::string> rp_id, GetTokenCallback callback, CtapDeviceResponseCode status, base::Optional<pin::KeyAgreementResponse> key) { @@ -140,6 +236,16 @@ void FidoDeviceAuthenticator::OnHaveEphemeralKeyForGetPINToken( std::move(callback).Run(status, base::nullopt); return; } + + if (Options()->supports_pin_uv_auth_token) { + pin::PinTokenWithPermissionsRequest request(pin, *key, permissions, rp_id); + std::array<uint8_t, 32> shared_key = request.shared_key(); + RunOperation<pin::PinTokenWithPermissionsRequest, pin::TokenResponse>( + std::move(request), std::move(callback), + base::BindOnce(&pin::TokenResponse::Parse, std::move(shared_key))); + return; + } + pin::PinTokenRequest request(pin, *key); std::array<uint8_t, 32> shared_key = request.shared_key(); RunOperation<pin::PinTokenRequest, pin::TokenResponse>( @@ -216,7 +322,6 @@ FidoDeviceAuthenticator::WillNeedPINToMakeCredential( if (Options()->user_verification_availability == AuthenticatorSupportedOptions::UserVerificationAvailability:: kSupportedAndConfigured) { - // TODO(crbug.com/1056317): implement inline bioenrollment. return device_support == ClientPinAvailability::kSupportedAndPinSet && can_collect_pin ? MakeCredentialPINDisposition::kUsePINForFallback @@ -226,11 +331,12 @@ FidoDeviceAuthenticator::WillNeedPINToMakeCredential( // CTAP 2.0 requires a PIN for credential creation once a PIN has been set. // Thus, if fallback to U2F isn't possible, a PIN will be needed if set. - const bool supports_u2f = + const bool u2f_fallback_possible = device()->device_info() && - device()->device_info()->versions.contains(ProtocolVersion::kU2f); + device()->device_info()->versions.contains(ProtocolVersion::kU2f) && + IsConvertibleToU2fRegisterCommand(request); if (device_support == ClientPinAvailability::kSupportedAndPinSet && - !supports_u2f) { + !u2f_fallback_possible) { if (can_collect_pin) { return MakeCredentialPINDisposition::kUsePIN; } else { @@ -257,7 +363,7 @@ FidoDeviceAuthenticator::WillNeedPINToMakeCredential( // else the device supports U2F (because the alternative was handled above) // and we'll use a U2F fallback to create a credential without a PIN. DCHECK(device_support != ClientPinAvailability::kSupportedAndPinSet || - supports_u2f); + u2f_fallback_possible); // TODO(agl): perhaps CTAP2 is indicated when, for example, hmac-secret is // requested? if (request.user_verification == UserVerificationRequirement::kDiscouraged) { @@ -326,10 +432,7 @@ void FidoDeviceAuthenticator::GetCredentialsMetadata( RunOperation<CredentialManagementRequest, CredentialsMetadataResponse>( CredentialManagementRequest::ForGetCredsMetadata( - Options()->supports_credential_management - ? CredentialManagementRequest::kDefault - : CredentialManagementRequest::kPreview, - pin_token), + GetCredentialManagementRequestVersion(*Options()), pin_token), std::move(callback), base::BindOnce(&CredentialsMetadataResponse::Parse)); } @@ -359,10 +462,7 @@ void FidoDeviceAuthenticator::EnumerateCredentials( state.callback = std::move(callback); RunOperation<CredentialManagementRequest, EnumerateRPsResponse>( CredentialManagementRequest::ForEnumerateRPsBegin( - Options()->supports_credential_management - ? CredentialManagementRequest::kDefault - : CredentialManagementRequest::kPreview, - pin_token), + GetCredentialManagementRequestVersion(*Options()), pin_token), base::BindOnce(&FidoDeviceAuthenticator::OnEnumerateRPsDone, weak_factory_.GetWeakPtr(), std::move(state)), base::BindOnce(&EnumerateRPsResponse::Parse, /*expect_rp_count=*/true), @@ -394,9 +494,9 @@ void FidoDeviceAuthenticator::OperationClearProxy( // RunTask starts a |FidoTask| and ensures that |task_| is reset when the given // callback is called. -template <typename Task, typename Request, typename Response> +template <typename Task, typename Response, typename... RequestArgs> void FidoDeviceAuthenticator::RunTask( - Request request, + RequestArgs&&... request_args, base::OnceCallback<void(CtapDeviceResponseCode, base::Optional<Response>)> callback) { DCHECK(!task_); @@ -405,7 +505,7 @@ void FidoDeviceAuthenticator::RunTask( << "InitializeAuthenticator() must be called first."; task_ = std::make_unique<Task>( - device_.get(), std::move(request), + device_.get(), std::forward<RequestArgs>(request_args)..., base::BindOnce( &FidoDeviceAuthenticator::TaskClearProxy<CtapDeviceResponseCode, base::Optional<Response>>, @@ -459,10 +559,8 @@ void FidoDeviceAuthenticator::OnEnumerateRPsDone( state.responses.emplace_back(std::move(*response->rp)); auto request = CredentialManagementRequest::ForEnumerateCredentialsBegin( - Options()->supports_credential_management - ? CredentialManagementRequest::kDefault - : CredentialManagementRequest::kPreview, - state.pin_token, std::move(*response->rp_id_hash)); + GetCredentialManagementRequestVersion(*Options()), state.pin_token, + std::move(*response->rp_id_hash)); RunOperation<CredentialManagementRequest, EnumerateCredentialsResponse>( std::move(request), base::BindOnce(&FidoDeviceAuthenticator::OnEnumerateCredentialsDone, @@ -490,9 +588,7 @@ void FidoDeviceAuthenticator::OnEnumerateCredentialsDone( state.current_rp_credential_count) { RunOperation<CredentialManagementRequest, EnumerateCredentialsResponse>( CredentialManagementRequest::ForEnumerateCredentialsGetNext( - Options()->supports_credential_management - ? CredentialManagementRequest::kDefault - : CredentialManagementRequest::kPreview), + GetCredentialManagementRequestVersion(*Options())), base::BindOnce(&FidoDeviceAuthenticator::OnEnumerateCredentialsDone, weak_factory_.GetWeakPtr(), std::move(state)), base::BindOnce(&EnumerateCredentialsResponse::Parse, @@ -504,9 +600,7 @@ void FidoDeviceAuthenticator::OnEnumerateCredentialsDone( if (state.responses.size() < state.rp_count) { RunOperation<CredentialManagementRequest, EnumerateRPsResponse>( CredentialManagementRequest::ForEnumerateRPsGetNext( - Options()->supports_credential_management - ? CredentialManagementRequest::kDefault - : CredentialManagementRequest::kPreview), + GetCredentialManagementRequestVersion(*Options())), base::BindOnce(&FidoDeviceAuthenticator::OnEnumerateRPsDone, weak_factory_.GetWeakPtr(), std::move(state)), base::BindOnce(&EnumerateRPsResponse::Parse, @@ -529,24 +623,12 @@ void FidoDeviceAuthenticator::DeleteCredential( RunOperation<CredentialManagementRequest, DeleteCredentialResponse>( CredentialManagementRequest::ForDeleteCredential( - Options()->supports_credential_management - ? CredentialManagementRequest::kDefault - : CredentialManagementRequest::kPreview, - pin_token, credential_id), + GetCredentialManagementRequestVersion(*Options()), pin_token, + credential_id), std::move(callback), base::BindOnce(&DeleteCredentialResponse::Parse), /*string_fixup_predicate=*/nullptr); } -// Helper method for determining correct bio enrollment version. -static BioEnrollmentRequest::Version GetBioEnrollmentRequestVersion( - const AuthenticatorSupportedOptions& options) { - return options.bio_enrollment_availability != - AuthenticatorSupportedOptions::BioEnrollmentAvailability:: - kNotSupported - ? BioEnrollmentRequest::kDefault - : BioEnrollmentRequest::kPreview; -} - void FidoDeviceAuthenticator::GetModality(BioEnrollmentCallback callback) { RunOperation<BioEnrollmentRequest, BioEnrollmentResponse>( BioEnrollmentRequest::ForGetModality( @@ -614,6 +696,22 @@ void FidoDeviceAuthenticator::BioEnrollEnumerate( std::move(callback), base::BindOnce(&BioEnrollmentResponse::Parse)); } +base::Optional<base::span<const int32_t>> +FidoDeviceAuthenticator::GetAlgorithms() { + if (device_->supported_protocol() == ProtocolVersion::kU2f) { + static constexpr int32_t kU2fAlgorithms[1] = { + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256)}; + return kU2fAlgorithms; + } + + const base::Optional<AuthenticatorGetInfoResponse>& get_info_response = + device_->device_info(); + if (get_info_response) { + return get_info_response->algorithms; + } + return base::nullopt; +} + void FidoDeviceAuthenticator::Reset(ResetCallback callback) { DCHECK(device_->SupportedProtocolIsInitialized()) << "InitializeAuthenticator() must be called first."; @@ -645,6 +743,13 @@ ProtocolVersion FidoDeviceAuthenticator::SupportedProtocol() const { return device_->supported_protocol(); } +bool FidoDeviceAuthenticator::SupportsHMACSecretExtension() const { + const base::Optional<AuthenticatorGetInfoResponse>& get_info_response = + device_->device_info(); + return get_info_response && get_info_response->extensions && + base::Contains(*get_info_response->extensions, kExtensionHmacSecret); +} + const base::Optional<AuthenticatorSupportedOptions>& FidoDeviceAuthenticator::Options() const { return options_; @@ -695,13 +800,22 @@ void FidoDeviceAuthenticator::GetUvRetries(GetRetriesCallback callback) { base::BindOnce(&pin::RetriesResponse::ParseUvRetries)); } -void FidoDeviceAuthenticator::GetUvToken(GetTokenCallback callback) { - GetEphemeralKey( - base::BindOnce(&FidoDeviceAuthenticator::OnHaveEphemeralKeyForUvToken, - weak_factory_.GetWeakPtr(), std::move(callback))); +bool FidoDeviceAuthenticator::CanGetUvToken() { + return options_->user_verification_availability == + AuthenticatorSupportedOptions::UserVerificationAvailability:: + kSupportedAndConfigured && + options_->supports_pin_uv_auth_token; +} + +void FidoDeviceAuthenticator::GetUvToken(base::Optional<std::string> rp_id, + GetTokenCallback callback) { + GetEphemeralKey(base::BindOnce( + &FidoDeviceAuthenticator::OnHaveEphemeralKeyForUvToken, + weak_factory_.GetWeakPtr(), std::move(rp_id), std::move(callback))); } void FidoDeviceAuthenticator::OnHaveEphemeralKeyForUvToken( + base::Optional<std::string> rp_id, GetTokenCallback callback, CtapDeviceResponseCode status, base::Optional<pin::KeyAgreementResponse> key) { @@ -712,7 +826,7 @@ void FidoDeviceAuthenticator::OnHaveEphemeralKeyForUvToken( DCHECK(key); - pin::UvTokenRequest request(*key); + pin::UvTokenRequest request(*key, std::move(rp_id)); std::array<uint8_t, 32> shared_key = request.shared_key(); RunOperation<pin::UvTokenRequest, pin::TokenResponse>( std::move(request), std::move(callback), diff --git a/chromium/device/fido/fido_device_authenticator.h b/chromium/device/fido/fido_device_authenticator.h index 032b9301320..f1fb3a075fc 100644 --- a/chromium/device/fido/fido_device_authenticator.h +++ b/chromium/device/fido/fido_device_authenticator.h @@ -33,7 +33,7 @@ class FidoTask; class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator : public FidoAuthenticator { public: - FidoDeviceAuthenticator(std::unique_ptr<FidoDevice> device); + explicit FidoDeviceAuthenticator(std::unique_ptr<FidoDevice> device); ~FidoDeviceAuthenticator() override; // FidoAuthenticator: @@ -45,9 +45,14 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator void GetNextAssertion(GetAssertionCallback callback) override; void GetTouch(base::OnceCallback<void()> callback) override; void GetPinRetries(GetRetriesCallback callback) override; - void GetPINToken(std::string pin, GetTokenCallback callback) override; + void GetPINToken(std::string pin, + const std::vector<pin::Permissions>& permissions, + base::Optional<std::string> rp_id, + GetTokenCallback callback) override; void GetUvRetries(GetRetriesCallback callback) override; - void GetUvToken(GetTokenCallback callback) override; + bool CanGetUvToken() override; + void GetUvToken(base::Optional<std::string> rp_id, + GetTokenCallback callback) override; void SetPIN(const std::string& pin, SetPINCallback callback) override; void ChangePIN(const std::string& old_pin, @@ -87,11 +92,13 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator std::vector<uint8_t> template_id, BioEnrollmentCallback) override; + base::Optional<base::span<const int32_t>> GetAlgorithms() override; void Reset(ResetCallback callback) override; void Cancel() override; std::string GetId() const override; base::string16 GetDisplayName() const override; ProtocolVersion SupportedProtocol() const override; + bool SupportsHMACSecretExtension() const override; const base::Optional<AuthenticatorSupportedOptions>& Options() const override; base::Optional<FidoTransportProtocol> AuthenticatorTransport() const override; bool IsInPairingMode() const override; @@ -122,8 +129,13 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator base::Optional<pin::KeyAgreementResponse>)>; void InitializeAuthenticatorDone(base::OnceClosure callback); void GetEphemeralKey(GetEphemeralKeyCallback callback); + void OnHaveEphemeralKey(GetEphemeralKeyCallback callback, + CtapDeviceResponseCode status, + base::Optional<pin::KeyAgreementResponse> key); void OnHaveEphemeralKeyForGetPINToken( std::string pin, + uint8_t permissions, + base::Optional<std::string> rp_id, GetTokenCallback callback, CtapDeviceResponseCode status, base::Optional<pin::KeyAgreementResponse> key); @@ -139,6 +151,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator CtapDeviceResponseCode status, base::Optional<pin::KeyAgreementResponse> key); void OnHaveEphemeralKeyForUvToken( + base::Optional<std::string> rp_id, GetTokenCallback callback, CtapDeviceResponseCode status, base::Optional<pin::KeyAgreementResponse> key); @@ -148,8 +161,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator template <typename... Args> void OperationClearProxy(base::OnceCallback<void(Args...)> callback, Args... args); - template <typename Task, typename Request, typename Response> - void RunTask(Request request, + template <typename Task, typename Response, typename... RequestArgs> + void RunTask(RequestArgs&&... request_args, base::OnceCallback<void(CtapDeviceResponseCode, base::Optional<Response>)> callback); template <typename Request, typename Response> @@ -174,6 +187,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator base::Optional<AuthenticatorSupportedOptions> options_; std::unique_ptr<FidoTask> task_; std::unique_ptr<GenericDeviceOperation> operation_; + base::Optional<pin::KeyAgreementResponse> cached_ephemeral_key_; base::WeakPtrFactory<FidoDeviceAuthenticator> weak_factory_{this}; DISALLOW_COPY_AND_ASSIGN(FidoDeviceAuthenticator); diff --git a/chromium/device/fido/fido_device_discovery.h b/chromium/device/fido/fido_device_discovery.h index 347c665abd0..f935f6fef12 100644 --- a/chromium/device/fido/fido_device_discovery.h +++ b/chromium/device/fido/fido_device_discovery.h @@ -13,7 +13,6 @@ #include <vector> #include "base/component_export.h" -#include "base/logging.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "base/strings/string_piece.h" diff --git a/chromium/device/fido/fido_discovery_base.h b/chromium/device/fido/fido_discovery_base.h index 649468150a8..8a29e3a9da5 100644 --- a/chromium/device/fido/fido_discovery_base.h +++ b/chromium/device/fido/fido_discovery_base.h @@ -7,8 +7,10 @@ #include <vector> +#include <ostream> + +#include "base/check.h" #include "base/component_export.h" -#include "base/logging.h" #include "base/macros.h" #include "device/fido/fido_transport_protocol.h" diff --git a/chromium/device/fido/fido_discovery_factory.cc b/chromium/device/fido/fido_discovery_factory.cc index a2e4e418e1f..539b0cb7693 100644 --- a/chromium/device/fido/fido_discovery_factory.cc +++ b/chromium/device/fido/fido_discovery_factory.cc @@ -4,8 +4,8 @@ #include "device/fido/fido_discovery_factory.h" -#include "base/logging.h" #include "base/notreached.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/fido/cable/fido_cable_discovery.h" #include "device/fido/features.h" #include "device/fido/fido_discovery_base.h" @@ -55,7 +55,8 @@ std::unique_ptr<FidoDiscoveryBase> FidoDiscoveryFactory::Create( case FidoTransportProtocol::kBluetoothLowEnergy: return nullptr; case FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy: - if (cable_data_.has_value() || qr_generator_key_.has_value()) { + if (device::BluetoothAdapterFactory::Get()->IsLowEnergySupported() && + (cable_data_.has_value() || qr_generator_key_.has_value())) { return std::make_unique<FidoCableDiscovery>( cable_data_.value_or(std::vector<CableDiscoveryData>()), qr_generator_key_, cable_pairing_callback_); diff --git a/chromium/device/fido/fido_request_handler_base.cc b/chromium/device/fido/fido_request_handler_base.cc index 896cecd966a..a7f687605d5 100644 --- a/chromium/device/fido/fido_request_handler_base.cc +++ b/chromium/device/fido/fido_request_handler_base.cc @@ -13,9 +13,9 @@ #include "base/strings/string_piece.h" #include "base/threading/sequenced_task_runner_handle.h" #include "base/time/time.h" -#include "base/timer/elapsed_timer.h" #include "build/build_config.h" #include "components/device_event_log/device_event_log.h" +#include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/fido/ble_adapter_manager.h" #include "device/fido/fido_authenticator.h" #include "device/fido/fido_discovery_factory.h" @@ -24,23 +24,8 @@ #include "device/fido/win/authenticator.h" #endif -namespace { -// Authenticators that return a response in less than this time are likely to -// have done so without interaction from the user. -static const base::TimeDelta kMinExpectedAuthenticatorResponseTime = - base::TimeDelta::FromMilliseconds(300); -} // namespace - namespace device { -// FidoRequestHandlerBase::AuthenticatorState --------------------------------- - -FidoRequestHandlerBase::AuthenticatorState::AuthenticatorState( - FidoAuthenticator* authenticator) - : authenticator(authenticator) {} - -FidoRequestHandlerBase::AuthenticatorState::~AuthenticatorState() = default; - // FidoRequestHandlerBase::TransportAvailabilityInfo -------------------------- FidoRequestHandlerBase::TransportAvailabilityInfo::TransportAvailabilityInfo() = @@ -91,8 +76,14 @@ void FidoRequestHandlerBase::InitDiscoveries( discoveries_.push_back(std::move(discovery)); } + // Check if the platform supports BLE before trying to get a power manager. + // CaBLE might be in |available_transports| without actual BLE support under + // the virtual environment. + // TODO(nsatragno): Move the BLE power manager logic to CableDiscoveryFactory + // so we don't need this additional check. bool has_ble = false; - if (base::Contains(transport_availability_info_.available_transports, + if (device::BluetoothAdapterFactory::Get()->IsLowEnergySupported() && + base::Contains(transport_availability_info_.available_transports, FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy)) { has_ble = true; base::SequencedTaskRunnerHandle::Get()->PostTask( @@ -176,7 +167,7 @@ void FidoRequestHandlerBase::CancelActiveAuthenticators( DCHECK(!task_it->first.empty()); if (task_it->first != exclude_device_id) { DCHECK(task_it->second); - task_it->second->authenticator->Cancel(); + task_it->second->Cancel(); // Note that the pointer being erased is non-owning. The actual // FidoAuthenticator instance is owned by its discovery (which in turn is @@ -227,20 +218,6 @@ void FidoRequestHandlerBase::Start() { discovery->Start(); } -bool FidoRequestHandlerBase::AuthenticatorMayHaveReturnedImmediately( - const std::string& authenticator_id) { - auto it = active_authenticators_.find(authenticator_id); - if (it == active_authenticators_.end()) - return false; - - if (!it->second->timer) - return true; - - FIDO_LOG(DEBUG) << "Authenticator returned in " - << it->second->timer->Elapsed(); - return it->second->timer->Elapsed() < kMinExpectedAuthenticatorResponseTime; -} - void FidoRequestHandlerBase::AuthenticatorRemoved( FidoDiscoveryBase* discovery, FidoAuthenticator* authenticator) { @@ -249,11 +226,18 @@ void FidoRequestHandlerBase::AuthenticatorRemoved( // 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(authenticator); - active_authenticators_.erase(authenticator->GetId()); - - if (observer_) + auto authenticator_it = active_authenticators_.find(authenticator->GetId()); + if (authenticator_it == active_authenticators_.end()) { + NOTREACHED(); + FIDO_LOG(ERROR) << "AuthenticatorRemoved() for unknown authenticator " + << authenticator->GetId(); + return; + } + DCHECK_EQ(authenticator_it->second, authenticator); + active_authenticators_.erase(authenticator_it); + if (observer_) { observer_->FidoAuthenticatorRemoved(authenticator->GetId()); + } } void FidoRequestHandlerBase::DiscoveryStarted( @@ -277,9 +261,8 @@ void FidoRequestHandlerBase::AuthenticatorAdded( FidoAuthenticator* authenticator) { DCHECK(!authenticator->GetId().empty()); bool was_inserted; - std::tie(std::ignore, was_inserted) = active_authenticators_.insert( - {authenticator->GetId(), - std::make_unique<AuthenticatorState>(authenticator)}); + std::tie(std::ignore, was_inserted) = + active_authenticators_.insert({authenticator->GetId(), authenticator}); if (!was_inserted) { NOTREACHED(); FIDO_LOG(ERROR) << "Authenticator with duplicate ID " @@ -341,11 +324,10 @@ void FidoRequestHandlerBase::InitializeAuthenticatorAndDispatchRequest( if (authenticator_it == active_authenticators_.end()) { return; } - AuthenticatorState* authenticator_state = authenticator_it->second.get(); - authenticator_state->timer = std::make_unique<base::ElapsedTimer>(); - authenticator_state->authenticator->InitializeAuthenticator(base::BindOnce( - &FidoRequestHandlerBase::DispatchRequest, weak_factory_.GetWeakPtr(), - authenticator_state->authenticator)); + FidoAuthenticator* authenticator = authenticator_it->second; + authenticator->InitializeAuthenticator( + base::BindOnce(&FidoRequestHandlerBase::DispatchRequest, + weak_factory_.GetWeakPtr(), authenticator)); } void FidoRequestHandlerBase::ConstructBleAdapterPowerManager() { @@ -358,4 +340,7 @@ void FidoRequestHandlerBase::StopDiscoveries() { } } +constexpr base::TimeDelta + FidoRequestHandlerBase::kMinExpectedAuthenticatorResponseTime; + } // namespace device diff --git a/chromium/device/fido/fido_request_handler_base.h b/chromium/device/fido/fido_request_handler_base.h index 55b06436450..11c2eaea9ed 100644 --- a/chromium/device/fido/fido_request_handler_base.h +++ b/chromium/device/fido/fido_request_handler_base.h @@ -14,9 +14,9 @@ #include <vector> #include "base/callback.h" +#include "base/check.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/optional.h" @@ -25,10 +25,6 @@ #include "device/fido/fido_discovery_base.h" #include "device/fido/fido_transport_protocol.h" -namespace base { -class ElapsedTimer; -} - namespace device { class BleAdapterManager; @@ -52,16 +48,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoRequestHandlerBase enum class RequestType { kMakeCredential, kGetAssertion }; - struct AuthenticatorState { - explicit AuthenticatorState(FidoAuthenticator* authenticator); - ~AuthenticatorState(); - - FidoAuthenticator* authenticator; - std::unique_ptr<base::ElapsedTimer> timer; - }; - using AuthenticatorMap = - std::map<std::string, std::unique_ptr<AuthenticatorState>, std::less<>>; + std::map<std::string, FidoAuthenticator*, std::less<>>; // Encapsulates data required to initiate WebAuthN UX dialog. Once all // components of TransportAvailabilityInfo is set, @@ -240,6 +228,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoRequestHandlerBase void StopDiscoveries(); protected: + // Authenticators that return a response in less than this time are likely to + // have done so without interaction from the user. + static constexpr base::TimeDelta kMinExpectedAuthenticatorResponseTime = + base::TimeDelta::FromMilliseconds(300); + // Subclasses implement this method to dispatch their request onto the given // FidoAuthenticator. The FidoAuthenticator is owned by this // FidoRequestHandler and stored in active_authenticators(). @@ -247,13 +240,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoRequestHandlerBase void Start(); - // Returns |true| if a short enough time has elapsed since the request was - // dispatched that an authenticator may be suspected to have returned a - // response without user interaction. - // Must be called after |DispatchRequest| is called. - bool AuthenticatorMayHaveReturnedImmediately( - const std::string& authenticator_id); - AuthenticatorMap& active_authenticators() { return active_authenticators_; } std::vector<std::unique_ptr<FidoDiscoveryBase>>& discoveries() { return discoveries_; diff --git a/chromium/device/fido/fido_test_data.h b/chromium/device/fido/fido_test_data.h index 840e815983c..71f73e09f30 100644 --- a/chromium/device/fido/fido_test_data.h +++ b/chromium/device/fido/fido_test_data.h @@ -1431,46 +1431,82 @@ constexpr uint8_t kTestMakeCredentialResponseWithIncorrectRpIdHash[] = { // Credential ID to be used in a request to yield the below // kTestGetAssertionResponse. -constexpr uint8_t kTestGetAssertionCredentialId[] = { - 0x9C, 0x06, 0x98, 0x05, 0xA7, 0xE9, 0x0C, 0xED, 0xF9, 0x24, 0xAC, - 0x5A, 0x29, 0x36, 0x95, 0xE0, 0x15, 0x46, 0x95, 0xBF, 0xFF, 0x99, - 0x1A, 0xA5, 0x40, 0xA8, 0x84, 0xAE, 0xF5, 0x42, 0xF3, 0x17, 0x78, - 0x51, 0xBE, 0x8A, 0x15, 0x2D, 0x48, 0x45, 0x2C, 0x0F, 0xE4, 0x67, - 0x29, 0x0C, 0x1B, 0xDA, 0xBE, 0x7C, 0xEB, 0xE5, 0xAD, 0x7A, 0xCA, - 0x6F, 0x76, 0x89, 0x38, 0x83, 0x2E, 0x65, 0x85, 0x1E, +constexpr uint8_t kTestGetAssertionCredentialId[64] = { + 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, }; constexpr uint8_t kTestGetAssertionResponse[] = { - 0x00, 0xA3, 0x01, 0xA2, 0x62, 0x69, 0x64, 0x58, 0x40, 0x9C, 0x06, 0x98, - 0x05, 0xA7, 0xE9, 0x0C, 0xED, 0xF9, 0x24, 0xAC, 0x5A, 0x29, 0x36, 0x95, - 0xE0, 0x15, 0x46, 0x95, 0xBF, 0xFF, 0x99, 0x1A, 0xA5, 0x40, 0xA8, 0x84, - 0xAE, 0xF5, 0x42, 0xF3, 0x17, 0x78, 0x51, 0xBE, 0x8A, 0x15, 0x2D, 0x48, - 0x45, 0x2C, 0x0F, 0xE4, 0x67, 0x29, 0x0C, 0x1B, 0xDA, 0xBE, 0x7C, 0xEB, - 0xE5, 0xAD, 0x7A, 0xCA, 0x6F, 0x76, 0x89, 0x38, 0x83, 0x2E, 0x65, 0x85, - 0x1E, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, - 0x63, 0x2D, 0x6B, 0x65, 0x79, 0x02, 0x58, 0x25, 0x11, 0x94, 0x22, 0x8d, - 0xa8, 0xfd, 0xbd, 0xee, 0xfd, 0x26, 0x1b, 0xd7, 0xb6, 0x59, 0x5c, 0xfd, - 0x70, 0xa5, 0x0d, 0x70, 0xc6, 0x40, 0x7b, 0xcf, 0x01, 0x3d, 0xe9, 0x6d, - 0x4e, 0xfb, 0x17, 0xde, 0x01, 0x00, 0x00, 0x00, 0x5F, 0x03, 0x58, 0x46, + // clang-format off + // See https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetAssertion#:~:text=the%20credential%20identifier%20whose%20private%20key + + // success + 0x00, + // map(3) + 0xA3, + // unsigned(1) -- credential + 0x01, + // map(2) + 0xA2, + // text(2) + 0x62, + // "id" + 0x69, 0x64, + // bytes(64) + 0x58, 0x40, + // credential ID (i.e. |kTestGetAssertionCredentialId|) + 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, + // text(4) + 0x64, + // "type" + 0x74, 0x79, 0x70, 0x65, + // text(10) + 0x6A, + // "public-key" + 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79, + // unsigned(2) -- authData + 0x02, + // bytes(37) + 0x58, 0x25, + // authData + 0x11, 0x94, 0x22, 0x8d, 0xa8, 0xfd, 0xbd, 0xee, 0xfd, 0x26, 0x1b, 0xd7, + 0xb6, 0x59, 0x5c, 0xfd, 0x70, 0xa5, 0x0d, 0x70, 0xc6, 0x40, 0x7b, 0xcf, + 0x01, 0x3d, 0xe9, 0x6d, 0x4e, 0xfb, 0x17, 0xde, 0x01, 0x00, 0x00, 0x00, + 0x5F, + // unsigned(3) -- signature + 0x03, + // bytes(70) + 0x58, 0x46, + // signature 0x30, 0x44, 0x02, 0x20, 0x62, 0xB8, 0xC4, 0x37, 0xB0, 0xB6, 0xFC, 0x89, 0x37, 0xF6, 0x45, 0xC0, 0x1E, 0x26, 0xCE, 0x0E, 0x26, 0x58, 0x38, 0xFE, 0xC4, 0xA8, 0x74, 0xC5, 0x5D, 0xDD, 0x6D, 0xEC, 0xF0, 0xA0, 0x83, 0xD3, 0x02, 0x20, 0x7C, 0xD4, 0x1C, 0xAF, 0x4F, 0xD8, 0x7F, 0x73, 0xBF, 0x01, 0x25, 0x06, 0x78, 0x11, 0x45, 0x2B, 0x5F, 0xB8, 0x17, 0xA3, 0xFA, 0x73, 0xB2, 0x17, 0x6B, 0xBD, 0x30, 0x36, 0x59, 0xC9, 0xCD, 0x92, + // clang-format on }; // {1: {"id": h'010203', "type": "public-key"}, 2: // h'1194228DA8FDBDEEFD261BD7B6595CFD70A50D70C6407BCF013DE96D4EFB17DE010000005F', // 3: h'101112', 4: {"id": h'01020304', "name": "..."}} constexpr uint8_t kTestGetAssertionResponseWithTruncatedUTF8[] = { - 0x00, 0xA4, 0x01, 0xA2, 0x62, 0x69, 0x64, 0x58, 0x40, 0x9C, 0x06, 0x98, - 0x05, 0xA7, 0xE9, 0x0C, 0xED, 0xF9, 0x24, 0xAC, 0x5A, 0x29, 0x36, 0x95, - 0xE0, 0x15, 0x46, 0x95, 0xBF, 0xFF, 0x99, 0x1A, 0xA5, 0x40, 0xA8, 0x84, - 0xAE, 0xF5, 0x42, 0xF3, 0x17, 0x78, 0x51, 0xBE, 0x8A, 0x15, 0x2D, 0x48, - 0x45, 0x2C, 0x0F, 0xE4, 0x67, 0x29, 0x0C, 0x1B, 0xDA, 0xBE, 0x7C, 0xEB, - 0xE5, 0xAD, 0x7A, 0xCA, 0x6F, 0x76, 0x89, 0x38, 0x83, 0x2E, 0x65, 0x85, - 0x1E, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, + 0x00, 0xA4, 0x01, 0xA2, 0x62, 0x69, 0x64, 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, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79, 0x02, 0x58, 0x25, 0x11, 0x94, 0x22, 0x8D, 0xA8, 0xFD, 0xBD, 0xEE, 0xFD, 0x26, 0x1B, 0xD7, 0xB6, 0x59, 0x5C, 0xFD, 0x70, 0xA5, 0x0D, 0x70, 0xC6, 0x40, 0x7B, 0xCF, 0x01, 0x3D, 0xE9, 0x6D, @@ -1488,13 +1524,13 @@ constexpr uint8_t kTestGetAssertionResponseWithTruncatedUTF8[] = { // h'1194228DA8FDBDEEFD261BD7B6595CFD70A50D70C6407BCF013DE96D4EFB17DE010000005F', // 3: h'101112', 4: {"id": h'01020304', "name": "..."}} constexpr uint8_t kTestGetAssertionResponseWithTruncatedAndInvalidUTF8[] = { - 0x00, 0xA4, 0x01, 0xA2, 0x62, 0x69, 0x64, 0x58, 0x40, 0x9C, 0x06, 0x98, - 0x05, 0xA7, 0xE9, 0x0C, 0xED, 0xF9, 0x24, 0xAC, 0x5A, 0x29, 0x36, 0x95, - 0xE0, 0x15, 0x46, 0x95, 0xBF, 0xFF, 0x99, 0x1A, 0xA5, 0x40, 0xA8, 0x84, - 0xAE, 0xF5, 0x42, 0xF3, 0x17, 0x78, 0x51, 0xBE, 0x8A, 0x15, 0x2D, 0x48, - 0x45, 0x2C, 0x0F, 0xE4, 0x67, 0x29, 0x0C, 0x1B, 0xDA, 0xBE, 0x7C, 0xEB, - 0xE5, 0xAD, 0x7A, 0xCA, 0x6F, 0x76, 0x89, 0x38, 0x83, 0x2E, 0x65, 0x85, - 0x1E, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, + 0x00, 0xA4, 0x01, 0xA2, 0x62, 0x69, 0x64, 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, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79, 0x02, 0x58, 0x25, 0x11, 0x94, 0x22, 0x8D, 0xA8, 0xFD, 0xBD, 0xEE, 0xFD, 0x26, 0x1B, 0xD7, 0xB6, 0x59, 0x5C, 0xFD, 0x70, 0xA5, 0x0D, 0x70, 0xC6, 0x40, 0x7B, 0xCF, 0x01, 0x3D, 0xE9, 0x6D, @@ -1523,13 +1559,13 @@ constexpr uint8_t kTestGetAssertionResponseWithEmptyCredential[] = { constexpr uint8_t kTestGetAssertionResponseWithIncorrectRpIdHash[] = { // clang-format off - 0x00, 0xA3, 0x01, 0xA2, 0x62, 0x69, 0x64, 0x58, 0x40, 0x9C, 0x06, 0x98, - 0x05, 0xA7, 0xE9, 0x0C, 0xED, 0xF9, 0x24, 0xAC, 0x5A, 0x29, 0x36, 0x95, - 0xE0, 0x15, 0x46, 0x95, 0xBF, 0xFF, 0x99, 0x1A, 0xA5, 0x40, 0xA8, 0x84, - 0xAE, 0xF5, 0x42, 0xF3, 0x17, 0x78, 0x51, 0xBE, 0x8A, 0x15, 0x2D, 0x48, - 0x45, 0x2C, 0x0F, 0xE4, 0x67, 0x29, 0x0C, 0x1B, 0xDA, 0xBE, 0x7C, 0xEB, - 0xE5, 0xAD, 0x7A, 0xCA, 0x6F, 0x76, 0x89, 0x38, 0x83, 0x2E, 0x65, 0x85, - 0x1E, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, + 0x00, 0xA3, 0x01, 0xA2, 0x62, 0x69, 0x64, 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, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6A, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x2D, 0x6B, 0x65, 0x79, 0x02, 0x58, 0x25, // Incorrect relying party ID hash 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, diff --git a/chromium/device/fido/get_assertion_handler_unittest.cc b/chromium/device/fido/get_assertion_handler_unittest.cc index c635a62311a..c516ddc55e0 100644 --- a/chromium/device/fido/get_assertion_handler_unittest.cc +++ b/chromium/device/fido/get_assertion_handler_unittest.cc @@ -57,6 +57,9 @@ class FidoGetAssertionHandlerTest : public ::testing::Test { FidoGetAssertionHandlerTest() { mock_adapter_ = base::MakeRefCounted<::testing::NiceMock<MockBluetoothAdapter>>(); + bluetooth_config_ = + BluetoothAdapterFactory::Get()->InitGlobalValuesForTesting(); + bluetooth_config_->SetLESupported(true); BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter_); } @@ -176,6 +179,8 @@ class FidoGetAssertionHandlerTest : public ::testing::Test { FidoTransportProtocol::kInternal, FidoTransportProtocol::kNearFieldCommunication, FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy}; + std::unique_ptr<BluetoothAdapterFactory::GlobalValuesForTesting> + bluetooth_config_; }; TEST_F(FidoGetAssertionHandlerTest, TransportAvailabilityInfo) { @@ -805,7 +810,7 @@ TEST(GetAssertionRequestHandlerWinTest, TestWinUsbDiscovery) { EXPECT_EQ(handler->AuthenticatorsForTesting().size(), 1u); EXPECT_EQ(handler->AuthenticatorsForTesting() .begin() - ->second->authenticator->IsWinNativeApiAuthenticator(), + ->second->IsWinNativeApiAuthenticator(), enable_api); } } diff --git a/chromium/device/fido/get_assertion_request_handler.cc b/chromium/device/fido/get_assertion_request_handler.cc index d231ba7c1a3..a7e9eb84208 100644 --- a/chromium/device/fido/get_assertion_request_handler.cc +++ b/chromium/device/fido/get_assertion_request_handler.cc @@ -13,6 +13,7 @@ #include "base/bind.h" #include "base/metrics/histogram_functions.h" #include "base/stl_util.h" +#include "base/timer/elapsed_timer.h" #include "build/build_config.h" #include "components/cbor/diagnostic_writer.h" #include "components/device_event_log/device_event_log.h" @@ -333,28 +334,20 @@ void GetAssertionRequestHandler::DispatchRequest( } CtapGetAssertionRequest request(request_); - if (authenticator->Options()) { - if (authenticator->Options()->user_verification_availability == - AuthenticatorSupportedOptions::UserVerificationAvailability:: - kSupportedAndConfigured && - request_.user_verification != - UserVerificationRequirement::kDiscouraged) { - if (authenticator->Options()->supports_uv_token) { - FIDO_LOG(DEBUG) << "Getting UV token from " - << authenticator->GetDisplayName(); - authenticator->GetUvToken( - base::BindOnce(&GetAssertionRequestHandler::OnHaveUvToken, - weak_factory_.GetWeakPtr(), authenticator)); - return; - } - request.user_verification = UserVerificationRequirement::kRequired; - } else { - request.user_verification = UserVerificationRequirement::kDiscouraged; - } - if (android_client_data_ext_ && authenticator->Options() && - authenticator->Options()->supports_android_client_data_ext) { - request.android_client_data_ext = *android_client_data_ext_; - } + if (request.user_verification != UserVerificationRequirement::kDiscouraged && + authenticator->CanGetUvToken()) { + FIDO_LOG(DEBUG) << "Getting UV token from " + << authenticator->GetDisplayName(); + authenticator->GetUvToken( + request_.rp_id, + base::BindOnce(&GetAssertionRequestHandler::OnHaveUvToken, + weak_factory_.GetWeakPtr(), authenticator)); + return; + } + + if (android_client_data_ext_ && authenticator->Options() && + authenticator->Options()->supports_android_client_data_ext) { + request.android_client_data_ext = *android_client_data_ext_; } ReportGetAssertionRequestTransport(authenticator); @@ -364,7 +357,8 @@ void GetAssertionRequestHandler::DispatchRequest( authenticator->GetAssertion( std::move(request), base::BindOnce(&GetAssertionRequestHandler::HandleResponse, - weak_factory_.GetWeakPtr(), authenticator)); + weak_factory_.GetWeakPtr(), authenticator, + base::ElapsedTimer())); } void GetAssertionRequestHandler::AuthenticatorAdded( @@ -407,6 +401,7 @@ void GetAssertionRequestHandler::AuthenticatorRemoved( void GetAssertionRequestHandler::HandleResponse( FidoAuthenticator* authenticator, + base::ElapsedTimer request_timer, CtapDeviceResponseCode status, base::Optional<AuthenticatorGetAssertionResponse> response) { DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_); @@ -457,9 +452,12 @@ void GetAssertionRequestHandler::HandleResponse( status == CtapDeviceResponseCode::kCtap2ErrOperationDenied) && authenticator->WillNeedPINToGetAssertion(request_, observer()) == PINDisposition::kUsePINForFallback) { - // Some authenticators will return this error immediately without user - // interaction when internal UV is locked. - if (AuthenticatorMayHaveReturnedImmediately(authenticator->GetId())) { + // Authenticators without uvToken support will return this error immediately + // without user interaction when internal UV is locked. + const base::TimeDelta response_time = request_timer.Elapsed(); + if (response_time < kMinExpectedAuthenticatorResponseTime) { + FIDO_LOG(DEBUG) << "Authenticator is probably locked, response_time=" + << response_time; authenticator->GetTouch(base::BindOnce( &GetAssertionRequestHandler::StartPINFallbackForInternalUv, weak_factory_.GetWeakPtr(), authenticator)); @@ -655,7 +653,7 @@ void GetAssertionRequestHandler::OnHavePIN(std::string pin) { state_ = State::kRequestWithPIN; authenticator_->GetPINToken( - std::move(pin), + std::move(pin), {pin::Permissions::kGetAssertion}, request_.rp_id, base::BindOnce(&GetAssertionRequestHandler::OnHavePINToken, weak_factory_.GetWeakPtr())); } @@ -721,6 +719,7 @@ void GetAssertionRequestHandler::OnUvRetriesResponse( } observer()->OnRetryUserVerification(response->retries); authenticator_->GetUvToken( + request_.rp_id, base::BindOnce(&GetAssertionRequestHandler::OnHaveUvToken, weak_factory_.GetWeakPtr(), authenticator_)); } @@ -788,8 +787,6 @@ void GetAssertionRequestHandler::DispatchRequestWithToken( CtapGetAssertionRequest request(request_); request.pin_auth = token.PinAuth(request.client_data_hash); request.pin_protocol = pin::kProtocolVersion; - // Do not do internal UV again. - request.user_verification = UserVerificationRequirement::kDiscouraged; if (android_client_data_ext_ && authenticator_->Options() && authenticator_->Options()->supports_android_client_data_ext) { @@ -801,7 +798,8 @@ void GetAssertionRequestHandler::DispatchRequestWithToken( authenticator_->GetAssertion( std::move(request), base::BindOnce(&GetAssertionRequestHandler::HandleResponse, - weak_factory_.GetWeakPtr(), authenticator_)); + weak_factory_.GetWeakPtr(), authenticator_, + base::ElapsedTimer())); } } // namespace device diff --git a/chromium/device/fido/get_assertion_request_handler.h b/chromium/device/fido/get_assertion_request_handler.h index d9077d5a37c..1b063ce2f48 100644 --- a/chromium/device/fido/get_assertion_request_handler.h +++ b/chromium/device/fido/get_assertion_request_handler.h @@ -19,6 +19,10 @@ #include "device/fido/fido_request_handler_base.h" #include "device/fido/fido_transport_protocol.h" +namespace base { +class ElapsedTimer; +} + namespace device { class FidoAuthenticator; @@ -86,6 +90,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionRequestHandler void HandleResponse( FidoAuthenticator* authenticator, + base::ElapsedTimer request_timer, CtapDeviceResponseCode response_code, base::Optional<AuthenticatorGetAssertionResponse> response); void HandleNextResponse( diff --git a/chromium/device/fido/get_assertion_task.cc b/chromium/device/fido/get_assertion_task.cc index cef806ca853..16d88c8a5a4 100644 --- a/chromium/device/fido/get_assertion_task.cc +++ b/chromium/device/fido/get_assertion_task.cc @@ -61,11 +61,15 @@ void GetAssertionTask::Cancel() { // static bool GetAssertionTask::StringFixupPredicate( const std::vector<const cbor::Value*>& path) { + // This filters out all elements that are not string-keyed, direct children + // of key 0x04, which is the `user` element of a getAssertion response. if (path.size() != 2 || !path[0]->is_unsigned() || path[0]->GetUnsigned() != 4 || !path[1]->is_string()) { return false; } + // Of those string-keyed children, only `name` and `displayName` may have + // truncated UTF-8 in their values. const std::string& user_key = path[1]->GetString(); return user_key == "name" || user_key == "displayName"; } diff --git a/chromium/device/fido/mac/fake_keychain.mm b/chromium/device/fido/mac/fake_keychain.mm index 1230468a89d..7d6892f2579 100644 --- a/chromium/device/fido/mac/fake_keychain.mm +++ b/chromium/device/fido/mac/fake_keychain.mm @@ -6,6 +6,7 @@ #include "device/fido/mac/fake_keychain.h" +#include "base/notreached.h" #include "device/fido/mac/keychain.h" namespace device { diff --git a/chromium/device/fido/mac/make_credential_operation.mm b/chromium/device/fido/mac/make_credential_operation.mm index 71ee5d4fc3f..fc27e4413fa 100644 --- a/chromium/device/fido/mac/make_credential_operation.mm +++ b/chromium/device/fido/mac/make_credential_operation.mm @@ -48,7 +48,7 @@ void MakeCredentialOperation::Run() { auto is_es256 = [](const PublicKeyCredentialParams::CredentialInfo& cred_info) { return cred_info.algorithm == - static_cast<int>(CoseAlgorithmIdentifier::kCoseEs256); + static_cast<int>(CoseAlgorithmIdentifier::kEs256); }; const auto& key_params = request_.public_key_credential_params.public_key_credential_params(); @@ -147,7 +147,7 @@ void MakeCredentialOperation::PromptTouchIdDone(bool success) { AttestationObject( std::move(authenticator_data), std::make_unique<PackedAttestationStatement>( - CoseAlgorithmIdentifier::kCoseEs256, std::move(*signature), + CoseAlgorithmIdentifier::kEs256, std::move(*signature), /*x509_certificates=*/std::vector<std::vector<uint8_t>>()))); std::move(callback_).Run(CtapDeviceResponseCode::kSuccess, std::move(response)); diff --git a/chromium/device/fido/mac/touch_id_context.mm b/chromium/device/fido/mac/touch_id_context.mm index 220354da77b..f9af4eb253a 100644 --- a/chromium/device/fido/mac/touch_id_context.mm +++ b/chromium/device/fido/mac/touch_id_context.mm @@ -129,9 +129,9 @@ bool TouchIdContext::TouchIdAvailableImpl(const AuthenticatorConfig& config) { } base::scoped_nsobject<LAContext> context([[LAContext alloc] init]); - base::scoped_nsobject<NSError> nserr; + NSError* nserr; if (![context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication - error:nserr.InitializeInto()]) { + error:&nserr]) { FIDO_LOG(DEBUG) << "canEvaluatePolicy failed: " << nserr; return false; } diff --git a/chromium/device/fido/mac/util.mm b/chromium/device/fido/mac/util.mm index 624081c4366..57c2e272306 100644 --- a/chromium/device/fido/mac/util.mm +++ b/chromium/device/fido/mac/util.mm @@ -55,7 +55,7 @@ std::unique_ptr<PublicKey> SecKeyRefToECPublicKey(SecKeyRef public_key_ref) base::span<const uint8_t> key_data = base::make_span(CFDataGetBytePtr(data_ref), CFDataGetLength(data_ref)); auto key = P256PublicKey::ParseX962Uncompressed( - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256), key_data); + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), key_data); if (!key) { LOG(ERROR) << "Unexpected public key format: " << base::HexEncode(key_data.data(), key_data.size()); diff --git a/chromium/device/fido/mac/util_unittest.cc b/chromium/device/fido/mac/util_unittest.cc index 322509b5871..3d6c9d41b50 100644 --- a/chromium/device/fido/mac/util_unittest.cc +++ b/chromium/device/fido/mac/util_unittest.cc @@ -23,7 +23,7 @@ namespace { std::unique_ptr<PublicKey> TestKey() { return P256PublicKey::ParseX962Uncompressed( - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256), + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), test_data::kX962UncompressedPublicKey); } diff --git a/chromium/device/fido/make_credential_handler_unittest.cc b/chromium/device/fido/make_credential_handler_unittest.cc index ffb1a2814fb..4d6d75e70c5 100644 --- a/chromium/device/fido/make_credential_handler_unittest.cc +++ b/chromium/device/fido/make_credential_handler_unittest.cc @@ -13,6 +13,7 @@ #include "components/cbor/values.h" #include "device/bluetooth/bluetooth_adapter_factory.h" #include "device/bluetooth/test/mock_bluetooth_adapter.h" +#include "device/fido/authenticator_get_info_response.h" #include "device/fido/authenticator_make_credential_response.h" #include "device/fido/authenticator_selection_criteria.h" #include "device/fido/ctap_make_credential_request.h" @@ -331,6 +332,50 @@ MATCHER(IsResidentKeyRequest, "") { return true; } +// Matches a CTAP command that is: +// * A valid make credential request, +// * if |is_uv| is true, +// * with an options map present, +// * and options.uv present and true. +// * if |is_uv_| is false, +// * with an options map not present, +// * or options.uv not present or false. +MATCHER_P(IsUvRequest, is_uv, "") { + if (arg.empty() || + arg[0] != base::strict_cast<uint8_t>( + CtapRequestCommand::kAuthenticatorMakeCredential)) { + *result_listener << "not make credential"; + return false; + } + + base::span<const uint8_t> param_bytes(arg); + param_bytes = param_bytes.subspan(1); + const auto maybe_map = cbor::Reader::Read(param_bytes); + if (!maybe_map || !maybe_map->is_map()) { + *result_listener << "not a map"; + return false; + } + const auto& map = maybe_map->GetMap(); + + const auto options_it = map.find(cbor::Value(7)); + if (options_it == map.end() || !options_it->second.is_map()) { + return is_uv == false; + } + const auto& options = options_it->second.GetMap(); + + const auto uv_it = options.find(cbor::Value("uv")); + if (uv_it == options.end()) { + return is_uv == false; + } + + if (!uv_it->second.is_bool()) { + *result_listener << "'uv' is not a boolean"; + return false; + } + + return uv_it->second.GetBool() == is_uv; +} + ACTION_P(Reply, reply) { base::ThreadTaskRunnerHandle::Get()->PostTask( FROM_HERE, @@ -641,6 +686,34 @@ TEST_F(FidoMakeCredentialHandlerTest, EXPECT_EQ(MakeCredentialStatus::kUserConsentDenied, callback().status()); } +TEST_F(FidoMakeCredentialHandlerTest, + TestCrossPlatformAuthenticatorsForceUVWhenSupported) { + const auto& test_info_response = test_data::kTestAuthenticatorGetInfoResponse; + ASSERT_EQ(ReadCTAPGetInfoResponse(test_info_response) + ->options.user_verification_availability, + AuthenticatorSupportedOptions::UserVerificationAvailability:: + kSupportedAndConfigured); + + auto device = + MockFidoDevice::MakeCtapWithGetInfoExpectation(test_info_response); + device->SetDeviceTransport(FidoTransportProtocol::kUsbHumanInterfaceDevice); + device->ExpectCtap2CommandAndRespondWith( + CtapRequestCommand::kAuthenticatorMakeCredential, + test_data::kTestMakeCredentialResponse, base::TimeDelta(), + IsUvRequest(true)); + + auto request_handler = + CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria( + AuthenticatorSelectionCriteria( + AuthenticatorAttachment::kAny, /*require_resident_key=*/false, + UserVerificationRequirement::kDiscouraged)); + discovery()->AddDevice(std::move(device)); + discovery()->WaitForCallToStartAndSimulateSuccess(); + + callback().WaitForCallback(); + EXPECT_EQ(MakeCredentialStatus::kSuccess, callback().status()); +} + // If a device returns CTAP2_ERR_PIN_AUTH_INVALID, the request should complete // with MakeCredentialStatus::kUserConsentDenied. TEST_F(FidoMakeCredentialHandlerTest, TestRequestWithPinAuthInvalid) { diff --git a/chromium/device/fido/make_credential_request_handler.cc b/chromium/device/fido/make_credential_request_handler.cc index 11cdaccff3d..56564970360 100644 --- a/chromium/device/fido/make_credential_request_handler.cc +++ b/chromium/device/fido/make_credential_request_handler.cc @@ -33,6 +33,15 @@ using MakeCredentialPINDisposition = namespace { +// Permissions requested for PinUvAuthToken. GetAssertion is needed for silent +// probing of credentials. +const std::vector<pin::Permissions> GetMakeCredentialRequestPermissions() { + static const std::vector<pin::Permissions> kMakeCredentialRequestPermissions = + {pin::Permissions::kMakeCredential, pin::Permissions::kGetAssertion, + pin::Permissions::kBioEnrollment}; + return kMakeCredentialRequestPermissions; +} + base::Optional<MakeCredentialStatus> ConvertDeviceResponseCode( CtapDeviceResponseCode device_response_code) { switch (device_response_code) { @@ -121,6 +130,31 @@ MakeCredentialStatus IsCandidateAuthenticatorPostTouch( return MakeCredentialStatus::kAuthenticatorMissingUserVerification; } + base::Optional<base::span<const int32_t>> supported_algorithms( + authenticator->GetAlgorithms()); + if (supported_algorithms) { + // Substitution of defaults should have happened by this point. + DCHECK(!request.public_key_credential_params.public_key_credential_params() + .empty()); + + bool at_least_one_common_algorithm = false; + for (const auto& algo : + request.public_key_credential_params.public_key_credential_params()) { + if (algo.type != CredentialType::kPublicKey) { + continue; + } + + if (base::Contains(*supported_algorithms, algo.algorithm)) { + at_least_one_common_algorithm = true; + break; + } + } + + if (!at_least_one_common_algorithm) { + return MakeCredentialStatus::kNoCommonAlgorithms; + } + } + return MakeCredentialStatus::kSuccess; } @@ -398,39 +432,28 @@ void MakeCredentialRequestHandler::DispatchRequest( } CtapMakeCredentialRequest request(request_); - if (authenticator->Options()) { - // If the authenticator has UV configured then UV will be required in - // order to create a credential (as specified by CTAP 2.0), even if - // user-verification is "discouraged". However, if the request is U2F-only - // then that doesn't apply and UV must be set to discouraged so that the - // request can be translated to U2F. Platform authenticators are exempted - // from this UV enforcement. - if (authenticator->Options()->user_verification_availability == - AuthenticatorSupportedOptions::UserVerificationAvailability:: - kSupportedAndConfigured && - !request_.is_u2f_only && - authenticator->AuthenticatorTransport() != - FidoTransportProtocol::kInternal) { - if (authenticator->Options()->supports_uv_token) { - authenticator->GetUvToken( - base::BindOnce(&MakeCredentialRequestHandler::OnHaveUvToken, - weak_factory_.GetWeakPtr(), authenticator)); - return; - } - request.user_verification = UserVerificationRequirement::kRequired; - } else { - request.user_verification = UserVerificationRequirement::kDiscouraged; - } + if (authenticator->Options()) { SpecializeRequestForAuthenticator(&request, authenticator); } + if (!request_.is_u2f_only && + request.user_verification != UserVerificationRequirement::kDiscouraged && + authenticator->CanGetUvToken()) { + authenticator->GetUvToken( + request_.rp.id, + base::BindOnce(&MakeCredentialRequestHandler::OnHaveUvToken, + weak_factory_.GetWeakPtr(), authenticator)); + return; + } + ReportMakeCredentialRequestTransport(authenticator); authenticator->MakeCredential( std::move(request), base::BindOnce(&MakeCredentialRequestHandler::HandleResponse, - weak_factory_.GetWeakPtr(), authenticator)); + weak_factory_.GetWeakPtr(), authenticator, + base::ElapsedTimer())); } void MakeCredentialRequestHandler::AuthenticatorRemoved( @@ -454,6 +477,7 @@ void MakeCredentialRequestHandler::AuthenticatorRemoved( void MakeCredentialRequestHandler::HandleResponse( FidoAuthenticator* authenticator, + base::ElapsedTimer request_timer, CtapDeviceResponseCode status, base::Optional<AuthenticatorMakeCredentialResponse> response) { DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_); @@ -501,9 +525,12 @@ void MakeCredentialRequestHandler::HandleResponse( status == CtapDeviceResponseCode::kCtap2ErrPinRequired) && authenticator->WillNeedPINToMakeCredential(request_, observer()) == MakeCredentialPINDisposition::kUsePINForFallback) { - // Some authenticators will return this error immediately without user - // interaction when internal UV is locked. - if (AuthenticatorMayHaveReturnedImmediately(authenticator->GetId())) { + // Authenticators without uvToken support will return this error immediately + // without user interaction when internal UV is locked. + const base::TimeDelta response_time = request_timer.Elapsed(); + if (response_time < kMinExpectedAuthenticatorResponseTime) { + FIDO_LOG(DEBUG) << "Authenticator is probably locked, response_time=" + << response_time; authenticator->GetTouch(base::BindOnce( &MakeCredentialRequestHandler::StartPINFallbackForInternalUv, weak_factory_.GetWeakPtr(), authenticator)); @@ -634,7 +661,7 @@ void MakeCredentialRequestHandler::OnHavePIN(std::string pin) { if (state_ == State::kWaitingForPIN) { state_ = State::kRequestWithPIN; authenticator_->GetPINToken( - std::move(pin), + std::move(pin), GetMakeCredentialRequestPermissions(), request_.rp.id, base::BindOnce(&MakeCredentialRequestHandler::OnHavePINToken, weak_factory_.GetWeakPtr())); return; @@ -690,7 +717,7 @@ void MakeCredentialRequestHandler::OnHaveSetPIN( // get a PIN token. state_ = State::kRequestWithPIN; authenticator_->GetPINToken( - std::move(pin), + std::move(pin), GetMakeCredentialRequestPermissions(), request_.rp.id, base::BindOnce(&MakeCredentialRequestHandler::OnHavePINToken, weak_factory_.GetWeakPtr())); } @@ -813,6 +840,7 @@ void MakeCredentialRequestHandler::OnUvRetriesResponse( } observer()->OnRetryUserVerification(response->retries); authenticator_->GetUvToken( + request_.rp.id, base::BindOnce(&MakeCredentialRequestHandler::OnHaveUvToken, weak_factory_.GetWeakPtr(), authenticator_)); } @@ -876,8 +904,6 @@ void MakeCredentialRequestHandler::DispatchRequestWithToken( CtapMakeCredentialRequest request(request_); request.pin_auth = token.PinAuth(request.client_data_hash); request.pin_protocol = pin::kProtocolVersion; - // Do not do internal UV again. - request.user_verification = UserVerificationRequirement::kDiscouraged; SpecializeRequestForAuthenticator(&request, authenticator_); ReportMakeCredentialRequestTransport(authenticator_); @@ -885,7 +911,8 @@ void MakeCredentialRequestHandler::DispatchRequestWithToken( authenticator_->MakeCredential( std::move(request), base::BindOnce(&MakeCredentialRequestHandler::HandleResponse, - weak_factory_.GetWeakPtr(), authenticator_)); + weak_factory_.GetWeakPtr(), authenticator_, + base::ElapsedTimer())); } void MakeCredentialRequestHandler::SpecializeRequestForAuthenticator( @@ -902,6 +929,10 @@ void MakeCredentialRequestHandler::SpecializeRequestForAuthenticator( authenticator->Options()->supports_android_client_data_ext) { request->android_client_data_ext = *options_.android_client_data_ext; } + + if (request->hmac_secret && !authenticator->SupportsHMACSecretExtension()) { + request->hmac_secret = false; + } } } // namespace device diff --git a/chromium/device/fido/make_credential_request_handler.h b/chromium/device/fido/make_credential_request_handler.h index 08499623700..48fa11b1fdd 100644 --- a/chromium/device/fido/make_credential_request_handler.h +++ b/chromium/device/fido/make_credential_request_handler.h @@ -26,6 +26,10 @@ #include "device/fido/fido_transport_protocol.h" #include "device/fido/pin.h" +namespace base { +class ElapsedTimer; +} + namespace device { class FidoAuthenticator; @@ -51,6 +55,7 @@ enum class MakeCredentialStatus { // there's no UI support for collecting a PIN. This could // be clearer. kAuthenticatorMissingUserVerification, + kNoCommonAlgorithms, kStorageFull, kWinInvalidStateError, kWinNotAllowedError, @@ -121,6 +126,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialRequestHandler void HandleResponse( FidoAuthenticator* authenticator, + base::ElapsedTimer request_timer, CtapDeviceResponseCode response_code, base::Optional<AuthenticatorMakeCredentialResponse> response); void CollectPINThenSendRequest(FidoAuthenticator* authenticator); diff --git a/chromium/device/fido/make_credential_task.cc b/chromium/device/fido/make_credential_task.cc index 4cb8ca45cde..04ac74c937c 100644 --- a/chromium/device/fido/make_credential_task.cc +++ b/chromium/device/fido/make_credential_task.cc @@ -28,6 +28,10 @@ namespace { bool CtapDeviceShouldUseU2fBecauseClientPinIsSet( const FidoDevice* device, const CtapMakeCredentialRequest& request) { + if (!IsConvertibleToU2fRegisterCommand(request)) { + return false; + } + DCHECK_EQ(device->supported_protocol(), ProtocolVersion::kCtap2); // Don't use U2F for requests that require UV or PIN which U2F doesn't // support. Note that |pin_auth| may also be set by GetTouchRequest(), but we @@ -79,7 +83,7 @@ CtapMakeCredentialRequest MakeCredentialTask::GetTouchRequest( std::move(user), PublicKeyCredentialParams( {{CredentialType::kPublicKey, - base::strict_cast<int>(CoseAlgorithmIdentifier::kCoseEs256)}})); + base::strict_cast<int>(CoseAlgorithmIdentifier::kEs256)}})); // If a device supports CTAP2 and has PIN support then setting an empty // pinAuth should trigger just a touch[1]. Our U2F code also understands diff --git a/chromium/device/fido/make_credential_task_unittest.cc b/chromium/device/fido/make_credential_task_unittest.cc index 95a5cf2d940..a4e7049f7e4 100644 --- a/chromium/device/fido/make_credential_task_unittest.cc +++ b/chromium/device/fido/make_credential_task_unittest.cc @@ -41,7 +41,7 @@ using TestMakeCredentialTaskCallback = class FidoMakeCredentialTaskTest : public testing::Test { public: - FidoMakeCredentialTaskTest() {} + FidoMakeCredentialTaskTest() = default; std::unique_ptr<MakeCredentialTask> CreateMakeCredentialTask( FidoDevice* device) { @@ -117,7 +117,8 @@ TEST_F(FidoMakeCredentialTaskTest, FallbackToU2fRegisterSuccess) { TEST_F(FidoMakeCredentialTaskTest, DefaultToU2fWhenClientPinSet) { AuthenticatorGetInfoResponse device_info( - {ProtocolVersion::kCtap2, ProtocolVersion::kU2f}, kTestDeviceAaguid); + {ProtocolVersion::kCtap2, ProtocolVersion::kU2f}, + {Ctap2Version::kCtap2_0}, kTestDeviceAaguid); AuthenticatorSupportedOptions options; options.client_pin_availability = AuthenticatorSupportedOptions::ClientPinAvailability::kSupportedAndPinSet; @@ -138,7 +139,8 @@ TEST_F(FidoMakeCredentialTaskTest, DefaultToU2fWhenClientPinSet) { TEST_F(FidoMakeCredentialTaskTest, EnforceClientPinWhenUserVerificationSet) { AuthenticatorGetInfoResponse device_info( - {ProtocolVersion::kCtap2, ProtocolVersion::kU2f}, kTestDeviceAaguid); + {ProtocolVersion::kCtap2, ProtocolVersion::kU2f}, + {Ctap2Version::kCtap2_0}, kTestDeviceAaguid); AuthenticatorSupportedOptions options; options.client_pin_availability = AuthenticatorSupportedOptions::ClientPinAvailability::kSupportedAndPinSet; diff --git a/chromium/device/fido/mock_fido_device.cc b/chromium/device/fido/mock_fido_device.cc index f74015a7600..d5562e29b37 100644 --- a/chromium/device/fido/mock_fido_device.cc +++ b/chromium/device/fido/mock_fido_device.cc @@ -86,12 +86,12 @@ std::vector<uint8_t> MockFidoDevice::EncodeCBORRequest( return request_bytes; } -// Matcher to compare the fist byte of the incoming requests. +// Matcher to compare the first byte of the incoming requests. MATCHER_P(IsCtap2Command, expected_command, "") { return !arg.empty() && arg[0] == base::strict_cast<uint8_t>(expected_command); } -MockFidoDevice::MockFidoDevice() {} +MockFidoDevice::MockFidoDevice() = default; MockFidoDevice::MockFidoDevice( ProtocolVersion protocol_version, base::Optional<AuthenticatorGetInfoResponse> device_info) @@ -138,14 +138,17 @@ void MockFidoDevice::StubGetId() { void MockFidoDevice::ExpectCtap2CommandAndRespondWith( CtapRequestCommand command, base::Optional<base::span<const uint8_t>> response, - base::TimeDelta delay) { + base::TimeDelta delay, + testing::Matcher<base::span<const uint8_t>> request_matcher) { auto data = fido_parsing_utils::MaterializeOrNull(response); auto send_response = [ data(std::move(data)), delay ](DeviceCallback & cb) { base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::BindOnce(std::move(cb), std::move(data)), delay); }; - EXPECT_CALL(*this, DeviceTransactPtr(IsCtap2Command(command), ::testing::_)) + EXPECT_CALL(*this, + DeviceTransactPtr(AllOf(IsCtap2Command(command), request_matcher), + ::testing::_)) .WillOnce(::testing::DoAll( ::testing::WithArg<1>(::testing::Invoke(send_response)), ::testing::Return(0))); diff --git a/chromium/device/fido/mock_fido_device.h b/chromium/device/fido/mock_fido_device.h index 1cb93d8b1db..90fa9956ed0 100644 --- a/chromium/device/fido/mock_fido_device.h +++ b/chromium/device/fido/mock_fido_device.h @@ -85,7 +85,9 @@ class MockFidoDevice : public ::testing::StrictMock<FidoDevice> { void ExpectCtap2CommandAndRespondWith( CtapRequestCommand command, base::Optional<base::span<const uint8_t>> response, - base::TimeDelta delay = base::TimeDelta()); + base::TimeDelta delay = base::TimeDelta(), + testing::Matcher<base::span<const uint8_t>> request_matcher = + testing::A<base::span<const uint8_t>>()); void ExpectCtap2CommandAndRespondWithError( CtapRequestCommand command, CtapDeviceResponseCode response_code, diff --git a/chromium/device/fido/p256_public_key.cc b/chromium/device/fido/p256_public_key.cc index 2bc521cb226..a6e0a7ffef9 100644 --- a/chromium/device/fido/p256_public_key.cc +++ b/chromium/device/fido/p256_public_key.cc @@ -8,6 +8,7 @@ #include "components/cbor/writer.h" #include "components/device_event_log/device_event_log.h" +#include "device/fido/cbor_extract.h" #include "device/fido/fido_constants.h" #include "device/fido/public_key.h" #include "third_party/boringssl/src/include/openssl/bn.h" @@ -17,6 +18,11 @@ #include "third_party/boringssl/src/include/openssl/mem.h" #include "third_party/boringssl/src/include/openssl/obj.h" +using device::cbor_extract::IntKey; +using device::cbor_extract::Is; +using device::cbor_extract::StepOrByte; +using device::cbor_extract::Stop; + namespace device { // kFieldElementLength is the size of a P-256 field element. The field is @@ -72,39 +78,43 @@ std::unique_ptr<PublicKey> P256PublicKey::ExtractFromCOSEKey( int32_t algorithm, base::span<const uint8_t> cbor_bytes, const cbor::Value::MapValue& map) { - cbor::Value::MapValue::const_iterator it = - map.find(cbor::Value(static_cast<int64_t>(CoseKeyKey::kKty))); - if (it == map.end() || !it->second.is_integer() || - it->second.GetInteger() != static_cast<int64_t>(CoseKeyTypes::kEC2)) { - return nullptr; - } - - it = map.find(cbor::Value(static_cast<int64_t>(CoseKeyKey::kEllipticCurve))); - if (it == map.end() || !it->second.is_integer() || - it->second.GetInteger() != static_cast<int64_t>(CoseCurves::kP256)) { - return nullptr; - } - - cbor::Value::MapValue::const_iterator it_x = - map.find(cbor::Value(static_cast<int64_t>(CoseKeyKey::kEllipticX))); - cbor::Value::MapValue::const_iterator it_y = - map.find(cbor::Value(static_cast<int64_t>(CoseKeyKey::kEllipticY))); - if (it_x == map.end() || !it_x->second.is_bytestring() || it_y == map.end() || - !it_y->second.is_bytestring()) { + struct COSEKey { + const int64_t* kty; + const int64_t* crv; + const std::vector<uint8_t>* x; + const std::vector<uint8_t>* y; + } cose_key; + + static constexpr cbor_extract::StepOrByte<COSEKey> kSteps[] = { + // clang-format off + ELEMENT(Is::kRequired, COSEKey, kty), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kKty)), + + ELEMENT(Is::kRequired, COSEKey, crv), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kEllipticCurve)), + + ELEMENT(Is::kRequired, COSEKey, x), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kEllipticX)), + + ELEMENT(Is::kRequired, COSEKey, y), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kEllipticY)), + + Stop<COSEKey>(), + // clang-format on + }; + + if (!cbor_extract::Extract<COSEKey>(&cose_key, kSteps, map) || + *cose_key.kty != static_cast<int64_t>(CoseKeyTypes::kEC2) || + *cose_key.crv != static_cast<int64_t>(CoseCurves::kP256) || + cose_key.x->size() != kFieldElementLength || + cose_key.y->size() != kFieldElementLength) { return nullptr; } - const std::vector<uint8_t>& x(it_x->second.GetBytestring()); - const std::vector<uint8_t>& y(it_y->second.GetBytestring()); - - if (x.size() != kFieldElementLength || y.size() != kFieldElementLength) { - FIDO_LOG(ERROR) << "Incorrect length for P-256 COSE key coordinates"; - } - bssl::UniquePtr<BIGNUM> x_bn(BN_new()); bssl::UniquePtr<BIGNUM> y_bn(BN_new()); - if (!BN_bin2bn(x.data(), x.size(), x_bn.get()) || - !BN_bin2bn(y.data(), y.size(), y_bn.get())) { + if (!BN_bin2bn(cose_key.x->data(), cose_key.x->size(), x_bn.get()) || + !BN_bin2bn(cose_key.y->data(), cose_key.y->size(), y_bn.get())) { return nullptr; } diff --git a/chromium/device/fido/p256_public_key.h b/chromium/device/fido/p256_public_key.h index 4eeccb704d6..f9d5bec7324 100644 --- a/chromium/device/fido/p256_public_key.h +++ b/chromium/device/fido/p256_public_key.h @@ -15,7 +15,7 @@ namespace device { -class PublicKey; +struct PublicKey; struct COMPONENT_EXPORT(DEVICE_FIDO) P256PublicKey { static std::unique_ptr<PublicKey> ExtractFromU2fRegistrationResponse( diff --git a/chromium/device/fido/pin.cc b/chromium/device/fido/pin.cc index 6480e111017..ee93c4135ec 100644 --- a/chromium/device/fido/pin.cc +++ b/chromium/device/fido/pin.cc @@ -508,19 +508,65 @@ AsCTAPRequestValuePair(const PinTokenRequest& request) { }); } -UvTokenRequest::UvTokenRequest(const KeyAgreementResponse& peer_key) - : TokenRequest(peer_key) {} +PinTokenWithPermissionsRequest::PinTokenWithPermissionsRequest( + const std::string& pin, + const KeyAgreementResponse& peer_key, + const uint8_t permissions, + const base::Optional<std::string> rp_id) + : PinTokenRequest(pin, peer_key), + permissions_(permissions), + rp_id_(rp_id) {} + +// static +std::pair<CtapRequestCommand, base::Optional<cbor::Value>> +AsCTAPRequestValuePair(const PinTokenWithPermissionsRequest& request) { + uint8_t encrypted_pin[sizeof(request.pin_hash_)]; + Encrypt(request.shared_key_.data(), request.pin_hash_, encrypted_pin); + + return EncodePINCommand( + Subcommand::kGetPinUvAuthTokenUsingPinWithPermissions, + [&request, encrypted_pin](cbor::Value::MapValue* map) { + map->emplace(static_cast<int>(RequestKey::kKeyAgreement), + std::move(request.cose_key_)); + map->emplace( + static_cast<int>(RequestKey::kPINHashEnc), + base::span<const uint8_t>(encrypted_pin, sizeof(encrypted_pin))); + map->emplace(static_cast<int>(RequestKey::kPermissions), + std::move(request.permissions_)); + if (request.rp_id_) { + map->emplace(static_cast<int>(RequestKey::kPermissionsRPID), + *request.rp_id_); + } + }); +} + +PinTokenWithPermissionsRequest::~PinTokenWithPermissionsRequest() = default; + +PinTokenWithPermissionsRequest::PinTokenWithPermissionsRequest( + PinTokenWithPermissionsRequest&& other) = default; + +UvTokenRequest::UvTokenRequest(const KeyAgreementResponse& peer_key, + base::Optional<std::string> rp_id) + : TokenRequest(peer_key), rp_id_(rp_id) {} UvTokenRequest::~UvTokenRequest() = default; UvTokenRequest::UvTokenRequest(UvTokenRequest&& other) = default; +// static std::pair<CtapRequestCommand, base::Optional<cbor::Value>> AsCTAPRequestValuePair(const UvTokenRequest& request) { return EncodePINCommand( Subcommand::kGetUvToken, [&request](cbor::Value::MapValue* map) { map->emplace(static_cast<int>(RequestKey::kKeyAgreement), std::move(request.cose_key_)); + map->emplace(static_cast<int>(RequestKey::kPermissions), + static_cast<uint8_t>(Permissions::kMakeCredential) | + static_cast<uint8_t>(Permissions::kGetAssertion)); + if (request.rp_id_) { + map->emplace(static_cast<int>(RequestKey::kPermissionsRPID), + *request.rp_id_); + } }); } diff --git a/chromium/device/fido/pin.h b/chromium/device/fido/pin.h index da7a00faa1d..88d16f09205 100644 --- a/chromium/device/fido/pin.h +++ b/chromium/device/fido/pin.h @@ -24,6 +24,16 @@ namespace device { namespace pin { +// Permission list flags. See +// https://drafts.fidoalliance.org/fido-2/stable-links-to-latest/fido-client-to-authenticator-protocol.html#permissions +enum class Permissions : uint8_t { + kMakeCredential = 0x01, + kGetAssertion = 0x02, + kCredentialManagement = 0x04, + kBioEnrollment = 0x08, + kPlatformConfiguration = 0x10, +}; + // kProtocolVersion is the version of the PIN protocol that this code // implements. constexpr int kProtocolVersion = 1; @@ -166,19 +176,42 @@ class PinTokenRequest : public TokenRequest { friend std::pair<CtapRequestCommand, base::Optional<cbor::Value>> AsCTAPRequestValuePair(const PinTokenRequest&); - private: + protected: uint8_t pin_hash_[16]; }; +class PinTokenWithPermissionsRequest : public PinTokenRequest { + public: + PinTokenWithPermissionsRequest(const std::string& pin, + const KeyAgreementResponse& peer_key, + const uint8_t permissions, + const base::Optional<std::string> rp_id); + PinTokenWithPermissionsRequest(PinTokenWithPermissionsRequest&&); + PinTokenWithPermissionsRequest(const PinTokenWithPermissionsRequest&) = + delete; + ~PinTokenWithPermissionsRequest() override; + + friend std::pair<CtapRequestCommand, base::Optional<cbor::Value>> + AsCTAPRequestValuePair(const PinTokenWithPermissionsRequest&); + + private: + uint8_t permissions_; + base::Optional<std::string> rp_id_; +}; + class UvTokenRequest : public TokenRequest { public: - explicit UvTokenRequest(const KeyAgreementResponse& peer_key); + UvTokenRequest(const KeyAgreementResponse& peer_key, + base::Optional<std::string> rp_id); UvTokenRequest(UvTokenRequest&&); UvTokenRequest(const UvTokenRequest&) = delete; virtual ~UvTokenRequest(); friend std::pair<CtapRequestCommand, base::Optional<cbor::Value>> AsCTAPRequestValuePair(const UvTokenRequest&); + + private: + base::Optional<std::string> rp_id_; }; // TokenResponse represents the response to a pin-token request. In order to diff --git a/chromium/device/fido/pin_internal.h b/chromium/device/fido/pin_internal.h index 7c8e2e11e22..9124cb1c8aa 100644 --- a/chromium/device/fido/pin_internal.h +++ b/chromium/device/fido/pin_internal.h @@ -30,17 +30,23 @@ enum class Subcommand : uint8_t { kGetPINToken = 0x05, kGetUvToken = 0x06, kGetUvRetries = 0x07, + kSetMinPINLength = 0x08, + kGetPinUvAuthTokenUsingPinWithPermissions = 0x09, }; // RequestKey enumerates the keys in the top-level CBOR map for all PIN // commands. enum class RequestKey : int { - kProtocol = 1, - kSubcommand = 2, - kKeyAgreement = 3, - kPINAuth = 4, - kNewPINEnc = 5, - kPINHashEnc = 6, + kProtocol = 0x01, + kSubcommand = 0x02, + kKeyAgreement = 0x03, + kPINAuth = 0x04, + kNewPINEnc = 0x05, + kPINHashEnc = 0x06, + kMinPINLength = 0x07, + kMinPINLengthRPIDs = 0x08, + kPermissions = 0x09, + kPermissionsRPID = 0x0A, }; // ResponseKey enumerates the keys in the top-level CBOR map for all PIN diff --git a/chromium/device/fido/public_key.cc b/chromium/device/fido/public_key.cc index 39c639725b0..88c6f581027 100644 --- a/chromium/device/fido/public_key.cc +++ b/chromium/device/fido/public_key.cc @@ -11,25 +11,13 @@ namespace device { -PublicKey::~PublicKey() = default; - -PublicKey::PublicKey(int32_t algorithm, - base::span<const uint8_t> cbor_bytes, - base::Optional<std::vector<uint8_t>> der_bytes) - : algorithm_(algorithm), - cbor_bytes_(fido_parsing_utils::Materialize(cbor_bytes)), - der_bytes_(std::move(der_bytes)) {} - -int32_t PublicKey::algorithm() const { - return algorithm_; -} +PublicKey::PublicKey(int32_t in_algorithm, + base::span<const uint8_t> in_cose_key_bytes, + base::Optional<std::vector<uint8_t>> in_der_bytes) + : algorithm(in_algorithm), + cose_key_bytes(fido_parsing_utils::Materialize(in_cose_key_bytes)), + der_bytes(std::move(in_der_bytes)) {} -const std::vector<uint8_t>& PublicKey::cose_key_bytes() const { - return cbor_bytes_; -} - -const base::Optional<std::vector<uint8_t>>& PublicKey::der_bytes() const { - return der_bytes_; -} +PublicKey::~PublicKey() = default; } // namespace device diff --git a/chromium/device/fido/public_key.h b/chromium/device/fido/public_key.h index c3e51ab7269..f9e97012984 100644 --- a/chromium/device/fido/public_key.h +++ b/chromium/device/fido/public_key.h @@ -6,41 +6,36 @@ #define DEVICE_FIDO_PUBLIC_KEY_H_ #include <stdint.h> -#include <string> #include <vector> #include "base/component_export.h" #include "base/containers/span.h" #include "base/macros.h" +#include "base/optional.h" namespace device { // https://www.w3.org/TR/webauthn/#credentialpublickey -class COMPONENT_EXPORT(DEVICE_FIDO) PublicKey { - public: +struct COMPONENT_EXPORT(DEVICE_FIDO) PublicKey { PublicKey(int32_t algorithm, base::span<const uint8_t> cbor_bytes, base::Optional<std::vector<uint8_t>> der_bytes); - virtual ~PublicKey(); + ~PublicKey(); - // algorithm returns the COSE algorithm identifier for this public key. - int32_t algorithm() const; + // algorithm contains the COSE algorithm identifier for this public key. + const int32_t algorithm; - // The credential public key as a COSE_Key map as defined in Section 7 - // of https://tools.ietf.org/html/rfc8152. - const std::vector<uint8_t>& cose_key_bytes() const; + // cose_key_bytes contains the credential public key as a COSE_Key map as + // defined in Section 7 of https://tools.ietf.org/html/rfc8152. + const std::vector<uint8_t> cose_key_bytes; - // der_bytes returns an ASN.1, DER, SubjectPublicKeyInfo describing this + // der_bytes contains an ASN.1, DER, SubjectPublicKeyInfo describing this // public key, if possible. (WebAuthn can negotiate the use of unknown // public-key algorithms so not all public keys can be transformed into SPKI // form.) - const base::Optional<std::vector<uint8_t>>& der_bytes() const; + const base::Optional<std::vector<uint8_t>> der_bytes; private: - const int32_t algorithm_; - std::vector<uint8_t> cbor_bytes_; - base::Optional<std::vector<uint8_t>> der_bytes_; - DISALLOW_COPY_AND_ASSIGN(PublicKey); }; diff --git a/chromium/device/fido/public_key_credential_params.h b/chromium/device/fido/public_key_credential_params.h index e866c9398ec..c93ca32e61b 100644 --- a/chromium/device/fido/public_key_credential_params.h +++ b/chromium/device/fido/public_key_credential_params.h @@ -25,8 +25,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) PublicKeyCredentialParams { public: struct COMPONENT_EXPORT(DEVICE_FIDO) CredentialInfo { bool operator==(const CredentialInfo& other) const; + CredentialType type = CredentialType::kPublicKey; - int algorithm = base::strict_cast<int>(CoseAlgorithmIdentifier::kCoseEs256); + int32_t algorithm = + base::strict_cast<int32_t>(CoseAlgorithmIdentifier::kEs256); }; static base::Optional<PublicKeyCredentialParams> CreateFromCBORValue( diff --git a/chromium/device/fido/rsa_public_key.cc b/chromium/device/fido/rsa_public_key.cc index e4abd88f606..be9af40234d 100644 --- a/chromium/device/fido/rsa_public_key.cc +++ b/chromium/device/fido/rsa_public_key.cc @@ -7,6 +7,7 @@ #include <utility> #include "components/cbor/writer.h" +#include "device/fido/cbor_extract.h" #include "device/fido/fido_constants.h" #include "third_party/boringssl/src/include/openssl/bn.h" #include "third_party/boringssl/src/include/openssl/bytestring.h" @@ -15,6 +16,11 @@ #include "third_party/boringssl/src/include/openssl/obj.h" #include "third_party/boringssl/src/include/openssl/rsa.h" +using device::cbor_extract::IntKey; +using device::cbor_extract::Is; +using device::cbor_extract::StepOrByte; +using device::cbor_extract::Stop; + namespace device { // static @@ -23,30 +29,36 @@ std::unique_ptr<PublicKey> RSAPublicKey::ExtractFromCOSEKey( base::span<const uint8_t> cbor_bytes, const cbor::Value::MapValue& map) { // See https://tools.ietf.org/html/rfc8230#section-4 - cbor::Value::MapValue::const_iterator it = - map.find(cbor::Value(static_cast<int64_t>(CoseKeyKey::kKty))); - if (it == map.end() || !it->second.is_integer() || - it->second.GetInteger() != static_cast<int64_t>(CoseKeyTypes::kRSA)) { - return nullptr; - } + struct COSEKey { + const int64_t* kty; + const std::vector<uint8_t>* n; + const std::vector<uint8_t>* e; + } cose_key; + + static constexpr cbor_extract::StepOrByte<COSEKey> kSteps[] = { + // clang-format off + ELEMENT(Is::kRequired, COSEKey, kty), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kKty)), - cbor::Value::MapValue::const_iterator it_n = - map.find(cbor::Value(static_cast<int64_t>(CoseKeyKey::kRSAModulus))); - cbor::Value::MapValue::const_iterator it_e = map.find( - cbor::Value(static_cast<int64_t>(CoseKeyKey::kRSAPublicExponent))); + ELEMENT(Is::kRequired, COSEKey, n), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kRSAModulus)), - if (it_n == map.end() || !it_n->second.is_bytestring() || it_e == map.end() || - !it_e->second.is_bytestring()) { + ELEMENT(Is::kRequired, COSEKey, e), + IntKey<COSEKey>(static_cast<int>(CoseKeyKey::kRSAPublicExponent)), + + Stop<COSEKey>(), + // clang-format on + }; + + if (!cbor_extract::Extract<COSEKey>(&cose_key, kSteps, map) || + *cose_key.kty != static_cast<int64_t>(CoseKeyTypes::kRSA)) { return nullptr; } - const std::vector<uint8_t>& n(it_n->second.GetBytestring()); - const std::vector<uint8_t>& e(it_e->second.GetBytestring()); - bssl::UniquePtr<BIGNUM> n_bn(BN_new()); bssl::UniquePtr<BIGNUM> e_bn(BN_new()); - if (!BN_bin2bn(n.data(), n.size(), n_bn.get()) || - !BN_bin2bn(e.data(), e.size(), e_bn.get())) { + if (!BN_bin2bn(cose_key.n->data(), cose_key.n->size(), n_bn.get()) || + !BN_bin2bn(cose_key.e->data(), cose_key.e->size(), e_bn.get())) { return nullptr; } diff --git a/chromium/device/fido/u2f_command_constructor.cc b/chromium/device/fido/u2f_command_constructor.cc index e54cf880ff0..53a3cf4fbce 100644 --- a/chromium/device/fido/u2f_command_constructor.cc +++ b/chromium/device/fido/u2f_command_constructor.cc @@ -25,7 +25,7 @@ bool IsConvertibleToU2fRegisterCommand( public_key_credential_info.begin(), public_key_credential_info.end(), [](const auto& credential_info) { return credential_info.algorithm == - base::strict_cast<int>(CoseAlgorithmIdentifier::kCoseEs256); + base::strict_cast<int>(CoseAlgorithmIdentifier::kEs256); }); } diff --git a/chromium/device/fido/virtual_ctap2_device.cc b/chromium/device/fido/virtual_ctap2_device.cc index 6cea9e49edd..6a82f0679df 100644 --- a/chromium/device/fido/virtual_ctap2_device.cc +++ b/chromium/device/fido/virtual_ctap2_device.cc @@ -6,6 +6,7 @@ #include <algorithm> #include <array> +#include <memory> #include <string> #include <utility> @@ -54,6 +55,17 @@ constexpr std::array<uint8_t, kAaguidLength> kDeviceAaguid = { {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}}; +constexpr uint8_t kSupportedPermissionsMask = + static_cast<uint8_t>(pin::Permissions::kMakeCredential) | + static_cast<uint8_t>(pin::Permissions::kGetAssertion) | + static_cast<uint8_t>(pin::Permissions::kCredentialManagement) | + static_cast<uint8_t>(pin::Permissions::kBioEnrollment); + +struct PinUvAuthTokenPermissions { + uint8_t permissions; + base::Optional<std::string> rp_id; +}; + std::vector<uint8_t> ConstructResponse(CtapDeviceResponseCode response_code, base::span<const uint8_t> data) { std::vector<uint8_t> response{base::strict_cast<uint8_t>(response_code)}; @@ -61,6 +73,45 @@ std::vector<uint8_t> ConstructResponse(CtapDeviceResponseCode response_code, return response; } +// Returns true if the |permissions| parameter requires an explicit permissions +// RPID. +bool PermissionsRequireRPID(uint8_t permissions) { + return permissions & + static_cast<uint8_t>(pin::Permissions::kMakeCredential) || + permissions & static_cast<uint8_t>(pin::Permissions::kGetAssertion); +} + +CtapDeviceResponseCode ExtractPermissions( + const cbor::Value::MapValue& request_map, + PinUvAuthTokenPermissions& out_permissions) { + const auto permissions_it = request_map.find( + cbor::Value(static_cast<int>(pin::RequestKey::kPermissions))); + if (permissions_it == request_map.end() || + !permissions_it->second.is_unsigned()) { + return CtapDeviceResponseCode::kCtap2ErrMissingParameter; + } + out_permissions.permissions = permissions_it->second.GetUnsigned(); + if (out_permissions.permissions == 0) { + return CtapDeviceResponseCode::kCtap1ErrInvalidParameter; + } + + DCHECK((out_permissions.permissions & ~kSupportedPermissionsMask) == 0); + + const auto permissions_rpid_it = request_map.find( + cbor::Value(static_cast<int>(pin::RequestKey::kPermissionsRPID))); + if (permissions_rpid_it == request_map.end() && + PermissionsRequireRPID(out_permissions.permissions)) { + return CtapDeviceResponseCode::kCtap2ErrMissingParameter; + } + if (permissions_rpid_it != request_map.end()) { + if (!permissions_rpid_it->second.is_string()) { + return CtapDeviceResponseCode::kCtap2ErrMissingParameter; + } + out_permissions.rp_id = permissions_rpid_it->second.GetString(); + } + return CtapDeviceResponseCode::kSuccess; +} + void ReturnCtap2Response( FidoDevice::DeviceCallback cb, CtapDeviceResponseCode response_code, @@ -384,13 +435,8 @@ VirtualCtap2Device::Config& VirtualCtap2Device::Config::operator=( const Config&) = default; VirtualCtap2Device::Config::~Config() = default; -VirtualCtap2Device::VirtualCtap2Device() : VirtualFidoDevice() { - device_info_ = - AuthenticatorGetInfoResponse({ProtocolVersion::kCtap2}, kDeviceAaguid); - device_info_->algorithms = { - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256), - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseRs256), - }; +VirtualCtap2Device::VirtualCtap2Device() { + Init({ProtocolVersion::kCtap2}); } VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state, @@ -399,10 +445,9 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state, std::vector<ProtocolVersion> versions = {ProtocolVersion::kCtap2}; if (config.u2f_support) { versions.emplace_back(ProtocolVersion::kU2f); - u2f_device_.reset(new VirtualU2fDevice(NewReferenceToState())); + u2f_device_ = std::make_unique<VirtualU2fDevice>(NewReferenceToState()); } - device_info_ = - AuthenticatorGetInfoResponse(std::move(versions), kDeviceAaguid); + Init(std::move(versions)); AuthenticatorSupportedOptions options; bool options_updated = false; @@ -427,9 +472,12 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state, options.user_verification_availability = AuthenticatorSupportedOptions:: UserVerificationAvailability::kSupportedButNotConfigured; } - options.supports_uv_token = config.uv_token_support; } + options.supports_pin_uv_auth_token = config.pin_uv_auth_token_support; + DCHECK(!options.supports_pin_uv_auth_token || + base::Contains(config.ctap2_versions, Ctap2Version::kCtap2_1)); + if (config.resident_key_support) { options_updated = true; options.supports_resident_key = true; @@ -438,6 +486,7 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state, if (config.credential_management_support) { options_updated = true; options.supports_credential_management = true; + options.supports_credential_management_preview = true; } if (config.bio_enrollment_support) { @@ -483,14 +532,22 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state, device_info_->options = std::move(options); } + std::vector<std::string> extensions; + if (config.cred_protect_support) { - device_info_->extensions.emplace( - {std::string(device::kExtensionCredProtect)}); + extensions.emplace_back(device::kExtensionCredProtect); + } + + if (config.hmac_secret_support) { + extensions.emplace_back(device::kExtensionHmacSecret); } if (config.support_android_client_data_extension) { - device_info_->extensions.emplace( - {std::string(device::kExtensionAndroidClientData)}); + extensions.emplace_back(device::kExtensionAndroidClientData); + } + + if (!extensions.empty()) { + device_info_->extensions.emplace(std::move(extensions)); } if (config.max_credential_count_in_list > 0) { @@ -501,6 +558,11 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state, if (config.max_credential_id_length > 0) { device_info_->max_credential_id_length = config.max_credential_id_length; } + + if (config.support_invalid_for_testing_algorithm) { + device_info_->algorithms.push_back( + static_cast<int32_t>(CoseAlgorithmIdentifier::kInvalidForTesting)); + } } VirtualCtap2Device::~VirtualCtap2Device() = default; @@ -570,6 +632,7 @@ FidoDevice::CancelToken VirtualCtap2Device::DeviceTransact( break; } case CtapRequestCommand::kAuthenticatorCredentialManagement: + case CtapRequestCommand::kAuthenticatorCredentialManagementPreview: response_code = OnCredentialManagement(request_bytes, &response_data); break; case CtapRequestCommand::kAuthenticatorBioEnrollment: @@ -590,10 +653,21 @@ base::WeakPtr<FidoDevice> VirtualCtap2Device::GetWeakPtr() { return weak_factory_.GetWeakPtr(); } +void VirtualCtap2Device::Init(std::vector<ProtocolVersion> versions) { + device_info_ = AuthenticatorGetInfoResponse( + std::move(versions), config_.ctap2_versions, kDeviceAaguid); + device_info_->algorithms = { + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), + static_cast<int32_t>(CoseAlgorithmIdentifier::kEdDSA), + static_cast<int32_t>(CoseAlgorithmIdentifier::kRs256), + }; +} + base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::CheckUserVerification( bool is_make_credential, const AuthenticatorSupportedOptions& options, + const std::string& rp_id, const base::Optional<std::vector<uint8_t>>& pin_auth, const base::Optional<uint8_t>& pin_protocol, base::span<const uint8_t> pin_token, @@ -647,9 +721,11 @@ VirtualCtap2Device::CheckUserVerification( return CtapDeviceResponseCode::kCtap2ErrInvalidOption; } - // Step 4. + // "If authenticator is protected by some form of user verification:" bool uv = false; if (can_do_uv) { + // "If the request is passed with "uv" option, use built-in user + // verification method and verify the user." if (user_verification == UserVerificationRequirement::kRequired) { if (options.user_verification_availability == AuthenticatorSupportedOptions::UserVerificationAvailability:: @@ -669,11 +745,37 @@ VirtualCtap2Device::CheckUserVerification( } } + // "If pinUvAuthParam parameter is present and pinUvAuthProtocol is 1". if (pin_auth && (options.client_pin_availability == AuthenticatorSupportedOptions::ClientPinAvailability:: kSupportedAndPinSet || - options.supports_uv_token)) { + options.supports_pin_uv_auth_token)) { DCHECK(pin_protocol && *pin_protocol == 1); + + // "Verify that the pinUvAuthToken has the {mc,ga} permission, if not, + // return CTAP2_ERR_PIN_AUTH_INVALID." + auto permission = is_make_credential ? pin::Permissions::kMakeCredential + : pin::Permissions::kGetAssertion; + if (!(mutable_state()->pin_uv_token_permissions & + static_cast<uint8_t>(permission))) { + return CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid; + } + + // "If the pinUvAuthToken has a permissions RPID associated and it + // does not match the RPID in this request, return + // CTAP2_ERR_PIN_AUTH_INVALID." + if (mutable_state()->pin_uv_token_rpid && + mutable_state()->pin_uv_token_rpid != rp_id) { + return CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid; + } + + // "If the pinUvAuthToken does not have a permissions RPID associated, + // associate the request RPID as permissions RPID." + if (!mutable_state()->pin_uv_token_rpid) { + mutable_state()->pin_uv_token_rpid = rp_id; + } + + // Verify pinUvAuthParam. if (CheckPINToken(pin_token, *pin_auth, client_data_hash)) { uv = true; } else { @@ -712,7 +814,7 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential( bool user_verified; const base::Optional<CtapDeviceResponseCode> uv_error = CheckUserVerification( - true /* is makeCredential */, options, request.pin_auth, + true /* is makeCredential */, options, request.rp.id, request.pin_auth, request.pin_protocol, mutable_state()->pin_token, request.client_data_hash, request.user_verification, &user_verified); if (uv_error != CtapDeviceResponseCode::kSuccess) { @@ -756,11 +858,20 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential( switch (param.algorithm) { default: continue; - case static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256): - private_key = FreshP256Key(); + case static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256): + private_key = PrivateKey::FreshP256Key(); + break; + case static_cast<int32_t>(CoseAlgorithmIdentifier::kRs256): + private_key = PrivateKey::FreshRSAKey(); break; - case static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseRs256): - private_key = FreshRSAKey(); + case static_cast<int32_t>(CoseAlgorithmIdentifier::kEdDSA): + private_key = PrivateKey::FreshEd25519Key(); + break; + case static_cast<int32_t>(CoseAlgorithmIdentifier::kInvalidForTesting): + if (!config_.support_invalid_for_testing_algorithm) { + continue; + } + private_key = PrivateKey::FreshInvalidForTestingKey(); break; } break; @@ -785,11 +896,18 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential( } // Our key handles are simple hashes of the public key. - const auto key_handle = crypto::SHA256Hash(public_key->cose_key_bytes()); + const auto key_handle = crypto::SHA256Hash(public_key->cose_key_bytes); base::Optional<cbor::Value> extensions; cbor::Value::MapValue extensions_map; if (request.hmac_secret) { + if (!config_.hmac_secret_support) { + // Should not have been sent. Authenticators will normally ignore unknown + // extensions but Chromium should not make this mistake. + DLOG(ERROR) + << "Rejecting makeCredential due to unexpected hmac_secret extension"; + return base::nullopt; + } extensions_map.emplace(cbor::Value(kExtensionHmacSecret), cbor::Value(true)); } @@ -816,8 +934,8 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential( extensions = cbor::Value(std::move(extensions_map)); } - auto authenticator_data = ConstructAuthenticatorData( - rp_id_hash, user_verified, 01ul, + AuthenticatorData authenticator_data( + rp_id_hash, /*user_present=*/true, user_verified, 01ul, ConstructAttestedCredentialData(key_handle, std::move(public_key)), std::move(extensions)); @@ -941,7 +1059,7 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnGetAssertion( bool user_verified; const base::Optional<CtapDeviceResponseCode> uv_error = CheckUserVerification( - false /* not makeCredential */, options, request.pin_auth, + false /* not makeCredential */, options, request.rp_id, request.pin_auth, request.pin_protocol, mutable_state()->pin_token, request.client_data_hash, request.user_verification, &user_verified); if (uv_error != CtapDeviceResponseCode::kSuccess) { @@ -1058,9 +1176,9 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnGetAssertion( registration.second->private_key->GetPublicKey())); } - auto authenticator_data = ConstructAuthenticatorData( - rp_id_hash, user_verified, registration.second->counter, - std::move(opt_attested_cred_data), + AuthenticatorData authenticator_data( + rp_id_hash, /*user_present=*/true, user_verified, + registration.second->counter, std::move(opt_attested_cred_data), extensions ? base::make_optional(extensions->Clone()) : base::nullopt); base::Optional<std::string> opt_android_client_data_json; @@ -1158,7 +1276,7 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand( std::vector<uint8_t>* response) { if (device_info_->options.client_pin_availability == AuthenticatorSupportedOptions::ClientPinAvailability::kNotSupported && - !config_.uv_token_support) { + !config_.pin_uv_auth_token_support) { return CtapDeviceResponseCode::kCtap1ErrInvalidCommand; } @@ -1287,7 +1405,15 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand( break; } - case static_cast<int>(device::pin::Subcommand::kGetPINToken): { + case static_cast<int>(device::pin::Subcommand::kGetPINToken): + case static_cast<int>( + device::pin::Subcommand::kGetPinUvAuthTokenUsingPinWithPermissions): { + if (subcommand == + static_cast<int>(device::pin::Subcommand:: + kGetPinUvAuthTokenUsingPinWithPermissions) && + !config_.pin_uv_auth_token_support) { + return CtapDeviceResponseCode::kCtap1ErrInvalidCommand; + } const auto encrypted_pin_hash = GetPINBytestring(request_map, pin::RequestKey::kPINHashEnc); const auto peer_key = @@ -1298,6 +1424,31 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand( return CtapDeviceResponseCode::kCtap2ErrMissingParameter; } + PinUvAuthTokenPermissions permissions; + if (subcommand == + static_cast<int>(device::pin::Subcommand::kGetPINToken)) { + if (request_map.find(cbor::Value(static_cast<int>( + pin::RequestKey::kPermissions))) != request_map.end() || + request_map.find(cbor::Value(static_cast<int>( + pin::RequestKey::kPermissionsRPID))) != request_map.end()) { + return CtapDeviceResponseCode::kCtap1ErrInvalidParameter; + } + // Set default PinUvAuthToken permissions. + permissions.permissions = + static_cast<uint8_t>(pin::Permissions::kMakeCredential) | + static_cast<uint8_t>(pin::Permissions::kGetAssertion); + } else { + DCHECK_EQ( + subcommand, + static_cast<int>(device::pin::Subcommand:: + kGetPinUvAuthTokenUsingPinWithPermissions)); + CtapDeviceResponseCode response = + ExtractPermissions(request_map, permissions); + if (response != CtapDeviceResponseCode::kSuccess) { + return response; + } + } + uint8_t shared_key[SHA256_DIGEST_LENGTH]; if (!mutable_state()->ecdh_key) { // kGetKeyAgreement should have been called first. @@ -1313,6 +1464,11 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand( return err; }; + mutable_state()->pin_retries = kMaxPinRetries; + + mutable_state()->pin_uv_token_permissions = permissions.permissions; + mutable_state()->pin_uv_token_rpid = permissions.rp_id; + response_map.emplace( static_cast<int>(pin::ResponseKey::kPINToken), GenerateAndEncryptToken(shared_key, mutable_state()->pin_token)); @@ -1326,6 +1482,13 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand( return CtapDeviceResponseCode::kCtap2ErrMissingParameter; } + PinUvAuthTokenPermissions permissions; + CtapDeviceResponseCode response = + ExtractPermissions(request_map, permissions); + if (response != CtapDeviceResponseCode::kSuccess) { + return response; + } + if (device_info_->options.user_verification_availability == AuthenticatorSupportedOptions::UserVerificationAvailability:: kSupportedButNotConfigured) { @@ -1357,6 +1520,8 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand( mutable_state()->pin_retries = kMaxPinRetries; mutable_state()->uv_retries = kMaxUvRetries; + mutable_state()->pin_uv_token_permissions = permissions.permissions; + mutable_state()->pin_uv_token_rpid = permissions.rp_id; response_map.emplace( static_cast<int>(pin::ResponseKey::kPINToken), @@ -1652,6 +1817,8 @@ CtapDeviceResponseCode VirtualCtap2Device::OnBioEnrollment( using SubCmd = BioEnrollmentSubCommand; switch (*cmd) { + // TODO(crbug.com/1090415): some of these commands should be checking + // PinUvAuthToken. case SubCmd::kGetFingerprintSensorInfo: response_map.emplace( static_cast<int>(BioEnrollmentResponseKey::kModality), @@ -1831,7 +1998,7 @@ void VirtualCtap2Device::InitPendingRegistrations( registration.first))); base::Optional<cbor::Value> cose_key = cbor::Reader::Read( - registration.second.private_key->GetPublicKey()->cose_key_bytes()); + registration.second.private_key->GetPublicKey()->cose_key_bytes); response_map.emplace( static_cast<int>(CredentialManagementResponseKey::kPublicKey), cose_key->GetMap()); @@ -1874,34 +2041,4 @@ AttestedCredentialData VirtualCtap2Device::ConstructAttestedCredentialData( fido_parsing_utils::Materialize(key_handle), std::move(public_key)); } - -AuthenticatorData VirtualCtap2Device::ConstructAuthenticatorData( - base::span<const uint8_t, kRpIdHashLength> rp_id_hash, - bool user_verified, - uint32_t current_signature_count, - base::Optional<AttestedCredentialData> attested_credential_data, - base::Optional<cbor::Value> extensions) { - uint8_t flag = - base::strict_cast<uint8_t>(AuthenticatorData::Flag::kTestOfUserPresence); - if (user_verified) { - flag |= base::strict_cast<uint8_t>( - AuthenticatorData::Flag::kTestOfUserVerification); - } - if (attested_credential_data) - flag |= base::strict_cast<uint8_t>(AuthenticatorData::Flag::kAttestation); - if (extensions) { - flag |= base::strict_cast<uint8_t>( - AuthenticatorData::Flag::kExtensionDataIncluded); - } - - std::array<uint8_t, kSignCounterLength> signature_counter; - signature_counter[0] = (current_signature_count >> 24) & 0xff; - signature_counter[1] = (current_signature_count >> 16) & 0xff; - signature_counter[2] = (current_signature_count >> 8) & 0xff; - signature_counter[3] = (current_signature_count)&0xff; - - return AuthenticatorData(rp_id_hash, flag, signature_counter, - std::move(attested_credential_data), - std::move(extensions)); -} } // namespace device diff --git a/chromium/device/fido/virtual_ctap2_device.h b/chromium/device/fido/virtual_ctap2_device.h index f2ab064f05e..7f05fc3e443 100644 --- a/chromium/device/fido/virtual_ctap2_device.h +++ b/chromium/device/fido/virtual_ctap2_device.h @@ -37,14 +37,14 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualCtap2Device Config& operator=(const Config&); ~Config(); + base::flat_set<Ctap2Version> ctap2_versions = {Ctap2Version::kCtap2_0}; // u2f_support, if true, makes this device a dual-protocol (i.e. CTAP2 and // U2F) device. bool u2f_support = false; bool pin_support = false; bool is_platform_authenticator = false; bool internal_uv_support = false; - // Ignored if |internal_uv_support| is false. - bool uv_token_support = false; + bool pin_uv_auth_token_support = false; bool resident_key_support = false; bool credential_management_support = false; bool bio_enrollment_support = false; @@ -52,6 +52,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualCtap2Device uint8_t bio_enrollment_capacity = 10; uint8_t bio_enrollment_samples_required = 4; bool cred_protect_support = false; + bool hmac_secret_support = false; // force_cred_protect, if set and if |cred_protect_support| is true, is a // credProtect level that will be forced for all registrations. This @@ -131,6 +132,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualCtap2Device // unhashed client data for the authenticator to assemble and hash instead // of using the regular, already hashed value. bool send_unsolicited_android_client_data_extension = false; + + // support_invalid_for_testing_algorithm causes the + // |CoseAlgorithmIdentifier::kInvalidForTesting| public-key algorithm to be + // advertised and supported to aid testing of unknown public-key types. + bool support_invalid_for_testing_algorithm = false; }; VirtualCtap2Device(); @@ -144,11 +150,15 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualCtap2Device base::WeakPtr<FidoDevice> GetWeakPtr() override; private: + // Init performs initialization that's common across the constructors. + void Init(std::vector<ProtocolVersion> versions); + // CheckUserVerification implements the first, common steps of // makeCredential and getAssertion from the CTAP2 spec. base::Optional<CtapDeviceResponseCode> CheckUserVerification( bool is_make_credential, const AuthenticatorSupportedOptions& options, + const std::string& rp_id, const base::Optional<std::vector<uint8_t>>& pin_auth, const base::Optional<uint8_t>& pin_protocol, base::span<const uint8_t> pin_token, @@ -181,12 +191,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualCtap2Device AttestedCredentialData ConstructAttestedCredentialData( base::span<const uint8_t> key_handle, std::unique_ptr<PublicKey> public_key); - AuthenticatorData ConstructAuthenticatorData( - base::span<const uint8_t, kRpIdHashLength> rp_id_hash, - bool user_verified, - uint32_t current_signature_count, - base::Optional<AttestedCredentialData> attested_credential_data, - base::Optional<cbor::Value> extensions); std::unique_ptr<VirtualU2fDevice> u2f_device_; diff --git a/chromium/device/fido/virtual_ctap2_device_unittest.cc b/chromium/device/fido/virtual_ctap2_device_unittest.cc index 494a8fd39b9..c8b27854e78 100644 --- a/chromium/device/fido/virtual_ctap2_device_unittest.cc +++ b/chromium/device/fido/virtual_ctap2_device_unittest.cc @@ -17,6 +17,7 @@ #include "device/fido/fido_parsing_utils.h" #include "device/fido/fido_test_data.h" #include "device/fido/test_callback_receiver.h" +#include "net/cert/asn1_util.h" #include "net/cert/x509_certificate.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -186,6 +187,16 @@ TEST_F(VirtualCtap2DeviceTest, AttestationCertificateIsValid) { base::Time now = base::Time::Now(); EXPECT_LT(cert->valid_start(), now); EXPECT_GT(cert->valid_expiry(), now); + + bool present; + bool critical; + base::StringPiece contents; + ASSERT_TRUE(net::asn1::ExtractExtensionFromDERCert( + net::x509_util::CryptoBufferAsStringPiece(cert->cert_buffer()), + base::StringPiece("\x55\x1d\x13"), &present, &critical, &contents)); + EXPECT_TRUE(present); + EXPECT_TRUE(critical); + EXPECT_EQ(base::StringPiece("\x30\x03\x01\x01\x00", 5), contents); } } // namespace device diff --git a/chromium/device/fido/virtual_fido_device.cc b/chromium/device/fido/virtual_fido_device.cc index 6f4dcad4bb7..f01e9dc0f90 100644 --- a/chromium/device/fido/virtual_fido_device.cc +++ b/chromium/device/fido/virtual_fido_device.cc @@ -9,7 +9,9 @@ #include <utility> #include "base/bind.h" +#include "base/logging.h" #include "base/rand_util.h" +#include "base/strings/string_number_conversions.h" #include "components/cbor/values.h" #include "components/cbor/writer.h" #include "crypto/ec_private_key.h" @@ -50,7 +52,7 @@ constexpr uint8_t kAttestationKey[]{ // CBBFunctionToVector converts a BoringSSL function that writes to a CBB to one // that returns a std::vector. Invoke for a function, f, with: -// CBBFunctionToVector<decltype(f), f>(args, to, f); +// CBBFunctionToVector<decltype(&f), f>(args, to, f); template <typename F, F function, typename... Args> std::vector<uint8_t> CBBFunctionToVector(Args&&... args) { uint8_t* der = nullptr; @@ -101,7 +103,7 @@ class EVPBackedPrivateKey : public VirtualFidoDevice::PrivateKey { } std::vector<uint8_t> GetPKCS8PrivateKey() const override { - return CBBFunctionToVector<decltype(EVP_marshal_private_key), + return CBBFunctionToVector<decltype(&EVP_marshal_private_key), EVP_marshal_private_key>(pkey_.get()); } @@ -121,7 +123,7 @@ class P256PrivateKey : public EVPBackedPrivateKey { std::vector<uint8_t> GetX962PublicKey() const override { const EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(pkey_.get()); - return CBBFunctionToVector<decltype(EC_POINT_point2cbb), + return CBBFunctionToVector<decltype(&EC_POINT_point2cbb), EC_POINT_point2cbb>( EC_KEY_get0_group(ec_key), EC_KEY_get0_public_key(ec_key), POINT_CONVERSION_UNCOMPRESSED, /*ctx=*/nullptr); @@ -129,7 +131,7 @@ class P256PrivateKey : public EVPBackedPrivateKey { std::unique_ptr<PublicKey> GetPublicKey() const override { return P256PublicKey::ParseX962Uncompressed( - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseEs256), + static_cast<int32_t>(CoseAlgorithmIdentifier::kEs256), GetX962PublicKey()); } @@ -159,7 +161,7 @@ class RSAPrivateKey : public EVPBackedPrivateKey { cbor::Value::MapValue map; map.emplace(static_cast<int64_t>(CoseKeyKey::kAlg), - static_cast<int64_t>(CoseAlgorithmIdentifier::kCoseRs256)); + static_cast<int64_t>(CoseAlgorithmIdentifier::kRs256)); map.emplace(static_cast<int64_t>(CoseKeyKey::kKty), static_cast<int64_t>(CoseKeyTypes::kRSA)); map.emplace(static_cast<int64_t>(CoseKeyKey::kRSAModulus), @@ -171,11 +173,11 @@ class RSAPrivateKey : public EVPBackedPrivateKey { cbor::Writer::Write(cbor::Value(std::move(map)))); std::vector<uint8_t> der_bytes( - CBBFunctionToVector<decltype(EVP_marshal_public_key), + CBBFunctionToVector<decltype(&EVP_marshal_public_key), EVP_marshal_public_key>(pkey_.get())); return std::make_unique<PublicKey>( - static_cast<int32_t>(CoseAlgorithmIdentifier::kCoseRs256), *cbor_bytes, + static_cast<int32_t>(CoseAlgorithmIdentifier::kRs256), *cbor_bytes, std::move(der_bytes)); } @@ -185,6 +187,77 @@ class RSAPrivateKey : public EVPBackedPrivateKey { } }; +class Ed25519PrivateKey : public EVPBackedPrivateKey { + public: + Ed25519PrivateKey() + : EVPBackedPrivateKey(EVP_PKEY_ED25519, ConfigureKeyGen) {} + + explicit Ed25519PrivateKey(bssl::UniquePtr<EVP_PKEY> pkey) + : EVPBackedPrivateKey(std::move(pkey)) {} + + std::unique_ptr<PublicKey> GetPublicKey() const override { + uint8_t public_key[32]; + size_t public_key_len = sizeof(public_key); + CHECK( + EVP_PKEY_get_raw_public_key(pkey_.get(), public_key, &public_key_len) && + public_key_len == sizeof(public_key)); + + cbor::Value::MapValue map; + map.emplace(static_cast<int64_t>(CoseKeyKey::kAlg), + static_cast<int64_t>(CoseAlgorithmIdentifier::kEdDSA)); + map.emplace(static_cast<int64_t>(CoseKeyKey::kKty), + static_cast<int64_t>(CoseKeyTypes::kOKP)); + map.emplace(static_cast<int64_t>(CoseKeyKey::kEllipticCurve), + static_cast<int64_t>(CoseCurves::kEd25519)); + map.emplace(static_cast<int64_t>(CoseKeyKey::kEllipticX), + base::span<const uint8_t>(public_key, sizeof(public_key))); + + base::Optional<std::vector<uint8_t>> cbor_bytes( + cbor::Writer::Write(cbor::Value(std::move(map)))); + + std::vector<uint8_t> der_bytes( + CBBFunctionToVector<decltype(&EVP_marshal_public_key), + EVP_marshal_public_key>(pkey_.get())); + + return std::make_unique<PublicKey>( + static_cast<int32_t>(CoseAlgorithmIdentifier::kRs256), *cbor_bytes, + std::move(der_bytes)); + } + + private: + static int ConfigureKeyGen(EVP_PKEY_CTX* ctx) { return 1; } +}; + +class InvalidForTestingPrivateKey : public VirtualFidoDevice::PrivateKey { + public: + InvalidForTestingPrivateKey() = default; + + std::vector<uint8_t> Sign(base::span<const uint8_t> message) override { + return {'s', 'i', 'g'}; + } + + std::vector<uint8_t> GetPKCS8PrivateKey() const override { + CHECK(false); + return {}; + } + + std::unique_ptr<PublicKey> GetPublicKey() const override { + cbor::Value::MapValue map; + map.emplace( + static_cast<int64_t>(CoseKeyKey::kAlg), + static_cast<int64_t>(CoseAlgorithmIdentifier::kInvalidForTesting)); + map.emplace(static_cast<int64_t>(CoseKeyKey::kKty), + static_cast<int64_t>(CoseKeyTypes::kInvalidForTesting)); + + base::Optional<std::vector<uint8_t>> cbor_bytes( + cbor::Writer::Write(cbor::Value(std::move(map)))); + + return std::make_unique<PublicKey>( + static_cast<int32_t>(CoseAlgorithmIdentifier::kInvalidForTesting), + *cbor_bytes, base::nullopt); + } +}; + } // namespace // VirtualFidoDevice::PrivateKey ---------------------------------------------- @@ -222,11 +295,39 @@ VirtualFidoDevice::PrivateKey::FromPKCS8( case EVP_PKEY_RSA: return std::unique_ptr<PrivateKey>(new RSAPrivateKey(std::move(pkey))); + case EVP_PKEY_ED25519: + return std::unique_ptr<PrivateKey>( + new Ed25519PrivateKey(std::move(pkey))); + default: return base::nullopt; } } +// static +std::unique_ptr<VirtualFidoDevice::PrivateKey> +VirtualFidoDevice::PrivateKey::FreshP256Key() { + return std::make_unique<P256PrivateKey>(); +} + +// static +std::unique_ptr<VirtualFidoDevice::PrivateKey> +VirtualFidoDevice::PrivateKey::FreshRSAKey() { + return std::make_unique<RSAPrivateKey>(); +} + +// static +std::unique_ptr<VirtualFidoDevice::PrivateKey> +VirtualFidoDevice::PrivateKey::FreshEd25519Key() { + return std::make_unique<Ed25519PrivateKey>(); +} + +// static +std::unique_ptr<VirtualFidoDevice::PrivateKey> +VirtualFidoDevice::PrivateKey::FreshInvalidForTestingKey() { + return std::make_unique<InvalidForTestingPrivateKey>(); +} + // VirtualFidoDevice::RegistrationData ---------------------------------------- VirtualFidoDevice::RegistrationData::RegistrationData() = default; @@ -242,8 +343,9 @@ VirtualFidoDevice::RegistrationData::RegistrationData(RegistrationData&& data) = default; VirtualFidoDevice::RegistrationData::~RegistrationData() = default; -VirtualFidoDevice::RegistrationData& VirtualFidoDevice::RegistrationData:: -operator=(RegistrationData&& other) = default; +VirtualFidoDevice::RegistrationData& +VirtualFidoDevice::RegistrationData::operator=(RegistrationData&& other) = + default; // VirtualFidoDevice::State --------------------------------------------------- @@ -258,7 +360,7 @@ bool VirtualFidoDevice::State::InjectRegistration( auto application_parameter = fido_parsing_utils::CreateSHA256Hash(relying_party_id); - RegistrationData registration(FreshP256Key(), + RegistrationData registration(PrivateKey::FreshP256Key(), std::move(application_parameter), 0 /* signature counter */); @@ -304,7 +406,7 @@ bool VirtualFidoDevice::State::InjectResidentKey( device::PublicKeyCredentialUserEntity user) { return InjectResidentKey(std::move(credential_id), std::move(rp), std::move(user), /*signature_counter=*/0, - FreshP256Key()); + PrivateKey::FreshP256Key()); } bool VirtualFidoDevice::State::InjectResidentKey( @@ -336,18 +438,6 @@ std::vector<uint8_t> VirtualFidoDevice::GetAttestationKey() { return fido_parsing_utils::Materialize(kAttestationKey); } -// static -std::unique_ptr<VirtualFidoDevice::PrivateKey> -VirtualFidoDevice::FreshP256Key() { - return std::make_unique<P256PrivateKey>(); -} - -// static -std::unique_ptr<VirtualFidoDevice::PrivateKey> -VirtualFidoDevice::FreshRSAKey() { - return std::unique_ptr<PrivateKey>(new RSAPrivateKey); -} - bool VirtualFidoDevice::Sign(crypto::ECPrivateKey* private_key, base::span<const uint8_t> sign_buffer, std::vector<uint8_t>* signature) { @@ -387,8 +477,21 @@ VirtualFidoDevice::GenerateAttestationCertificate( 8 - transport_bit - 1, // trailing bits unused 0b10000000 >> transport_bit, // transport }; + + // https://www.w3.org/TR/webauthn/#packed-attestation-cert-requirements + // The Basic Constraints extension MUST have the CA component set to false. + static constexpr uint8_t kBasicContraintsOID[] = {0x55, 0x1d, 0x13}; + static constexpr uint8_t kBasicContraintsContents[] = { + 0x30, // SEQUENCE + 0x03, // three bytes long + 0x01, // BOOLEAN + 0x01, // one byte long + 0x00, // false + }; + const std::vector<net::x509_util::Extension> extensions = { - {kTransportTypesOID, false /* not critical */, kTransportTypesContents}, + {kTransportTypesOID, /*critical=*/false, kTransportTypesContents}, + {kBasicContraintsOID, /*critical=*/true, kBasicContraintsContents}, }; // https://w3c.github.io/webauthn/#sctn-packed-attestation-cert-requirements @@ -474,7 +577,9 @@ FidoTransportProtocol VirtualFidoDevice::DeviceTransport() const { // static std::string VirtualFidoDevice::MakeVirtualFidoDeviceId() { - return "VirtualFidoDevice-" + base::RandBytesAsString(32); + uint8_t rand_bytes[32]; + base::RandBytes(rand_bytes, sizeof(rand_bytes)); + return "VirtualFidoDevice-" + base::HexEncode(rand_bytes); } } // namespace device diff --git a/chromium/device/fido/virtual_fido_device.h b/chromium/device/fido/virtual_fido_device.h index ba6348e742a..e39677127ef 100644 --- a/chromium/device/fido/virtual_fido_device.h +++ b/chromium/device/fido/virtual_fido_device.h @@ -32,7 +32,7 @@ class ECPrivateKey; namespace device { -class PublicKey; +struct PublicKey; constexpr size_t kMaxPinRetries = 8; @@ -50,6 +50,20 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice { static base::Optional<std::unique_ptr<PrivateKey>> FromPKCS8( base::span<const uint8_t> pkcs8_private_key); + // FreshP256Key returns a randomly generated P-256 PrivateKey. + static std::unique_ptr<PrivateKey> FreshP256Key(); + + // FreshRSAKey returns a randomly generated RSA PrivateKey. + static std::unique_ptr<PrivateKey> FreshRSAKey(); + + // FreshEd25519Key returns a randomly generated Ed25519 PrivateKey. + static std::unique_ptr<PrivateKey> FreshEd25519Key(); + + // FreshInvalidForTestingKey returns a dummy |PrivateKey| with a special + // algorithm number that is used to test that unknown public keys are + // handled correctly. + static std::unique_ptr<PrivateKey> FreshInvalidForTestingKey(); + virtual ~PrivateKey(); // Sign returns a signature over |message|. @@ -151,6 +165,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice { // The random PIN token that is returned as a placeholder for the PIN // itself. uint8_t pin_token[32]; + // The permissions parameter for |pin_token|. + uint8_t pin_uv_token_permissions = 0; + // The permissions RPID for |pin_token|. + base::Optional<std::string> pin_uv_token_rpid; // Number of internal UV retries remaining. int uv_retries = kMaxUvRetries; @@ -258,8 +276,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice { scoped_refptr<State> NewReferenceToState() const { return state_; } - static std::unique_ptr<PrivateKey> FreshP256Key(); - static std::unique_ptr<PrivateKey> FreshRSAKey(); static bool Sign(crypto::ECPrivateKey* private_key, base::span<const uint8_t> sign_buffer, std::vector<uint8_t>* signature); diff --git a/chromium/device/fido/virtual_u2f_device.cc b/chromium/device/fido/virtual_u2f_device.cc index a0567217c8d..b8a77cdc3a2 100644 --- a/chromium/device/fido/virtual_u2f_device.cc +++ b/chromium/device/fido/virtual_u2f_device.cc @@ -143,7 +143,7 @@ base::Optional<std::vector<uint8_t>> VirtualU2fDevice::DoRegister( // Create key to register. // Note: Non-deterministic, you need to mock this out if you rely on // deterministic behavior. - std::unique_ptr<PrivateKey> private_key(FreshP256Key()); + std::unique_ptr<PrivateKey> private_key(PrivateKey::FreshP256Key()); const std::vector<uint8_t> x962 = private_key->GetX962PublicKey(); // Our key handles are simple hashes of the public key. diff --git a/chromium/device/fido/win/authenticator.cc b/chromium/device/fido/win/authenticator.cc index 6595896329a..29901515f03 100644 --- a/chromium/device/fido/win/authenticator.cc +++ b/chromium/device/fido/win/authenticator.cc @@ -188,6 +188,10 @@ bool WinWebAuthnApiAuthenticator::SupportsCredProtectExtension() const { return win_api_->Version() >= WEBAUTHN_API_VERSION_2; } +bool WinWebAuthnApiAuthenticator::SupportsHMACSecretExtension() const { + return true; +} + const base::Optional<AuthenticatorSupportedOptions>& WinWebAuthnApiAuthenticator::Options() const { // The request can potentially be fulfilled by any device that Windows diff --git a/chromium/device/fido/win/authenticator.h b/chromium/device/fido/win/authenticator.h index fd66a029c4e..fb3d59f14dd 100644 --- a/chromium/device/fido/win/authenticator.h +++ b/chromium/device/fido/win/authenticator.h @@ -63,6 +63,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) WinWebAuthnApiAuthenticator // SupportsCredProtectExtension returns whether the native API supports the // credProtect CTAP extension. bool SupportsCredProtectExtension() const override; + bool SupportsHMACSecretExtension() const override; const base::Optional<AuthenticatorSupportedOptions>& Options() const override; base::Optional<FidoTransportProtocol> AuthenticatorTransport() const override; bool IsWinNativeApiAuthenticator() const override; diff --git a/chromium/device/fido/win/fake_webauthn_api.cc b/chromium/device/fido/win/fake_webauthn_api.cc index 049ab511a58..3ec8e1ac95a 100644 --- a/chromium/device/fido/win/fake_webauthn_api.cc +++ b/chromium/device/fido/win/fake_webauthn_api.cc @@ -5,16 +5,65 @@ #include "device/fido/win/fake_webauthn_api.h" #include "base/check.h" +#include "base/containers/span.h" #include "base/notreached.h" #include "base/optional.h" +#include "base/stl_util.h" #include "base/strings/string16.h" +#include "base/strings/string_piece_forward.h" +#include "base/strings/utf_string_conversions.h" +#include "components/cbor/values.h" +#include "crypto/sha2.h" +#include "device/fido/attested_credential_data.h" +#include "device/fido/authenticator_data.h" #include "device/fido/fido_parsing_utils.h" #include "device/fido/fido_test_data.h" namespace device { +struct FakeWinWebAuthnApi::WebAuthnAssertionEx { + std::vector<uint8_t> credential_id; + std::vector<uint8_t> authenticator_data; + std::vector<uint8_t> signature; + WEBAUTHN_ASSERTION assertion; +}; + FakeWinWebAuthnApi::FakeWinWebAuthnApi() = default; -FakeWinWebAuthnApi::~FakeWinWebAuthnApi() = default; +FakeWinWebAuthnApi::~FakeWinWebAuthnApi() { + // Ensure callers free unmanaged pointers returned by the real Windows API. + DCHECK(returned_attestations_.empty()); + DCHECK(returned_assertions_.empty()); +} + +bool FakeWinWebAuthnApi::InjectNonDiscoverableCredential( + base::span<const uint8_t> credential_id, + const std::string& rp_id) { + bool was_inserted; + std::tie(std::ignore, was_inserted) = registrations_.insert( + {fido_parsing_utils::Materialize(credential_id), + RegistrationData(VirtualFidoDevice::PrivateKey::FreshP256Key(), + fido_parsing_utils::CreateSHA256Hash(rp_id), + /*counter=*/0)}); + return was_inserted; +} + +bool FakeWinWebAuthnApi::InjectDiscoverableCredential( + base::span<const uint8_t> credential_id, + device::PublicKeyCredentialRpEntity rp, + device::PublicKeyCredentialUserEntity user) { + RegistrationData registration(VirtualFidoDevice::PrivateKey::FreshP256Key(), + fido_parsing_utils::CreateSHA256Hash(rp.id), + /*counter=*/0); + registration.is_resident = true; + registration.user = std::move(user); + registration.rp = std::move(rp); + + bool was_inserted; + std::tie(std::ignore, was_inserted) = + registrations_.insert({fido_parsing_utils::Materialize(credential_id), + std::move(registration)}); + return was_inserted; +} bool FakeWinWebAuthnApi::IsAvailable() const { return is_available_; @@ -34,9 +83,15 @@ HRESULT FakeWinWebAuthnApi::AuthenticatorMakeCredential( PCWEBAUTHN_CLIENT_DATA client_data, PCWEBAUTHN_AUTHENTICATOR_MAKE_CREDENTIAL_OPTIONS options, PWEBAUTHN_CREDENTIAL_ATTESTATION* credential_attestation_ptr) { + // TODO(martinkr): Implement to create a credential in |registrations_|. DCHECK(is_available_); - *credential_attestation_ptr = &attestation_; - return result_; + if (result_override_ != S_OK) { + return result_override_; + } + + returned_attestations_.push_back(FakeAttestation()); + *credential_attestation_ptr = &returned_attestations_.back(); + return S_OK; } HRESULT FakeWinWebAuthnApi::AuthenticatorGetAssertion( @@ -45,9 +100,104 @@ HRESULT FakeWinWebAuthnApi::AuthenticatorGetAssertion( PCWEBAUTHN_CLIENT_DATA client_data, PCWEBAUTHN_AUTHENTICATOR_GET_ASSERTION_OPTIONS options, PWEBAUTHN_ASSERTION* assertion_ptr) { + // TODO(martinkr): support AppID extension DCHECK(is_available_); - *assertion_ptr = &assertion_; - return result_; + + if (result_override_ != S_OK) { + return result_override_; + } + + const auto rp_id_hash = + fido_parsing_utils::CreateSHA256Hash(base::UTF16ToUTF8(rp_id)); + + RegistrationData* registration = nullptr; + base::span<const uint8_t> credential_id; + PCWEBAUTHN_CREDENTIAL_LIST allow_credentials = options->pAllowCredentialList; + + // Find a matching resident credential if allow list is empty. Windows + // provides its own account selector, so only one credential gets returned. + // Pretend the user selected the first match. + if (allow_credentials->cCredentials == 0) { + for (auto& registration_pair : registrations_) { + if (!registration_pair.second.is_resident || + registration_pair.second.application_parameter != rp_id_hash) { + continue; + } + credential_id = registration_pair.first; + registration = ®istration_pair.second; + break; + } + } + + for (size_t i = 0; i < allow_credentials->cCredentials; i++) { + PWEBAUTHN_CREDENTIAL_EX credential = allow_credentials->ppCredentials[i]; + base::span<const uint8_t> allow_credential_id(credential->pbId, + credential->cbId); + auto it = registrations_.find(allow_credential_id); + if (it == registrations_.end() || + it->second.application_parameter != rp_id_hash) { + continue; + } + credential_id = it->first; + registration = &it->second; + break; + } + + if (!registration) { + return NTE_NOT_FOUND; + } + DCHECK(!credential_id.empty()); + + WebAuthnAssertionEx result; + result.credential_id = fido_parsing_utils::Materialize(credential_id); + result.authenticator_data = + AuthenticatorData( + registration->application_parameter, + /*user_present=*/true, + /*user_verified=*/options->dwUserVerificationRequirement != + WEBAUTHN_USER_VERIFICATION_REQUIREMENT_DISCOURAGED, + registration->counter++, + /*attested_credential_data=*/base::nullopt, + /*extensions=*/base::nullopt) + .SerializeToByteArray(); + + // Create the assertion signature. + std::vector<uint8_t> sign_data; + fido_parsing_utils::Append(&sign_data, result.authenticator_data); + fido_parsing_utils::Append( + &sign_data, crypto::SHA256Hash({client_data->pbClientDataJSON, + client_data->cbClientDataJSON})); + result.signature = + registration->private_key->Sign({sign_data.data(), sign_data.size()}); + + // Fill in the WEBAUTHN_ASSERTION struct returned to the caller. + result.assertion = {}; + result.assertion.dwVersion = 1; + result.assertion.cbAuthenticatorData = result.authenticator_data.size(); + result.assertion.pbAuthenticatorData = reinterpret_cast<PBYTE>( + const_cast<uint8_t*>(result.authenticator_data.data())); + + result.assertion.cbSignature = result.signature.size(); + result.assertion.pbSignature = result.signature.data(); + result.assertion.Credential = {}; + result.assertion.Credential.dwVersion = 1; + result.assertion.Credential.cbId = result.credential_id.size(); + result.assertion.Credential.pbId = result.credential_id.data(); + result.assertion.Credential.pwszCredentialType = + WEBAUTHN_CREDENTIAL_TYPE_PUBLIC_KEY; + // TODO(martinkr): Return a user entity for requests with empty allow lists. + // (Though the CTAP2.0 spec allows that to be omitted if only a single + // credential matched.) + result.assertion.pbUserId = nullptr; + result.assertion.cbUserId = 0; + + // The real API hands out results in naked pointers and asks callers + // to call FreeAssertion() when they're done. We maintain ownership + // of the pointees in |returned_assertions_|. + returned_assertions_.push_back(std::move(result)); + *assertion_ptr = &returned_assertions_.back().assertion; + + return S_OK; } HRESULT FakeWinWebAuthnApi::CancelCurrentOperation(GUID* cancellation_id) { @@ -82,9 +232,28 @@ PCWSTR FakeWinWebAuthnApi::GetErrorName(HRESULT hr) { } void FakeWinWebAuthnApi::FreeCredentialAttestation( - PWEBAUTHN_CREDENTIAL_ATTESTATION) {} + PWEBAUTHN_CREDENTIAL_ATTESTATION credential_attestation) { + for (auto it = returned_attestations_.begin(); + it != returned_attestations_.end(); ++it) { + if (credential_attestation != &*it) { + continue; + } + returned_attestations_.erase(it); + return; + } + NOTREACHED(); +} -void FakeWinWebAuthnApi::FreeAssertion(PWEBAUTHN_ASSERTION pWebAuthNAssertion) { +void FakeWinWebAuthnApi::FreeAssertion(PWEBAUTHN_ASSERTION assertion) { + for (auto it = returned_assertions_.begin(); it != returned_assertions_.end(); + ++it) { + if (assertion != &it->assertion) { + continue; + } + returned_assertions_.erase(it); + return; + } + NOTREACHED(); } int FakeWinWebAuthnApi::Version() { @@ -110,29 +279,4 @@ WEBAUTHN_CREDENTIAL_ATTESTATION FakeWinWebAuthnApi::FakeAttestation() { return attestation; } -// static -WEBAUTHN_ASSERTION FakeWinWebAuthnApi::FakeAssertion() { - WEBAUTHN_CREDENTIAL credential = {}; - // No constant macro available because 1 is the current version - credential.dwVersion = 1; - credential.cbId = sizeof(test_data::kCredentialId); - credential.pbId = - reinterpret_cast<PBYTE>(const_cast<uint8_t*>(test_data::kCredentialId)); - credential.pwszCredentialType = L"public-key"; - - WEBAUTHN_ASSERTION assertion = {}; - // No constant macro available because 1 is the current version - assertion.dwVersion = 1; - assertion.cbAuthenticatorData = sizeof(test_data::kTestSignAuthenticatorData); - assertion.pbAuthenticatorData = reinterpret_cast<PBYTE>( - const_cast<uint8_t*>(test_data::kTestSignAuthenticatorData)); - assertion.cbSignature = sizeof(test_data::kCtap2GetAssertionSignature); - assertion.pbSignature = reinterpret_cast<PBYTE>( - const_cast<uint8_t*>(test_data::kCtap2GetAssertionSignature)); - assertion.Credential = credential; - assertion.pbUserId = nullptr; - assertion.cbUserId = 0; - return assertion; -} - } // namespace device diff --git a/chromium/device/fido/win/fake_webauthn_api.h b/chromium/device/fido/win/fake_webauthn_api.h index 88a901df9f8..0e405c84a77 100644 --- a/chromium/device/fido/win/fake_webauthn_api.h +++ b/chromium/device/fido/win/fake_webauthn_api.h @@ -5,23 +5,55 @@ #ifndef DEVICE_FIDO_WIN_FAKE_WEBAUTHN_API_H_ #define DEVICE_FIDO_WIN_FAKE_WEBAUTHN_API_H_ +#include <stdint.h> +#include <map> +#include <memory> +#include <vector> + #include "base/component_export.h" #include "device/fido/public_key_credential_descriptor.h" #include "device/fido/public_key_credential_rp_entity.h" #include "device/fido/public_key_credential_user_entity.h" +#include "device/fido/virtual_fido_device.h" #include "device/fido/win/webauthn_api.h" namespace device { +// FakeWinWebAuthnApi is a test fake to use instead of the real Windows WebAuthn +// API implemented by webauthn.dll. +// +// The fake supports injecting discoverable and non-discoverable credentials +// that can be challenged via AuthenticatorGetAssertion(). +// AuthenticatorMakeCredential() returns a mock response and does not actually +// create a credential. +// +// Tests can inject a FakeWinWebAuthnApi via VirtualFidoDeviceFactory. class COMPONENT_EXPORT(DEVICE_FIDO) FakeWinWebAuthnApi : public WinWebAuthnApi { public: + using RegistrationData = VirtualFidoDevice::RegistrationData; + FakeWinWebAuthnApi(); ~FakeWinWebAuthnApi() override; + // Injects a non-discoverable credential that can be challenged with + // AuthenticatorGetAssertion(). + bool InjectNonDiscoverableCredential(base::span<const uint8_t> credential_id, + const std::string& relying_party_id); + + // Injects a discoverable credential that can be challenged with + // AuthenticatorGetAssertion(). + bool InjectDiscoverableCredential(base::span<const uint8_t> credential_id, + device::PublicKeyCredentialRpEntity rp, + device::PublicKeyCredentialUserEntity user); + // Inject the return value for WinWebAuthnApi::IsAvailable(). void set_available(bool available) { is_available_ = available; } - void set_hresult(HRESULT result) { result_ = result; } + // Injects an HRESULT to return from AuthenticatorMakeCredential() and + // AuthenticatorGetAssertion(). If set to anything other than |S_OK|, + // AuthenticatorGetAssertion() will immediately terminate the request with + // that value and not return a WEBAUTHN_ASSERTION. + void set_hresult(HRESULT result) { result_override_ = result; } // Inject the return value for // WinWebAuthnApi::IsUserverifyingPlatformAuthenticatorAvailable(). @@ -54,15 +86,24 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FakeWinWebAuthnApi : public WinWebAuthnApi { int Version() override; private: + struct WebAuthnAssertionEx; + static WEBAUTHN_CREDENTIAL_ATTESTATION FakeAttestation(); - static WEBAUTHN_ASSERTION FakeAssertion(); bool is_available_ = true; bool is_uvpaa_ = false; int version_ = WEBAUTHN_API_VERSION_2; - WEBAUTHN_CREDENTIAL_ATTESTATION attestation_ = FakeAttestation(); - WEBAUTHN_ASSERTION assertion_ = FakeAssertion(); - HRESULT result_ = S_OK; + HRESULT result_override_ = S_OK; + + // Owns the attestations returned by AuthenticatorMakeCredential(). + std::vector<WEBAUTHN_CREDENTIAL_ATTESTATION> returned_attestations_; + + // Owns assertions returned by AuthenticatorGetAssertion(). + std::vector<WebAuthnAssertionEx> returned_assertions_; + + std:: + map<std::vector<uint8_t>, RegistrationData, fido_parsing_utils::RangeLess> + registrations_; }; } // namespace device diff --git a/chromium/device/fido/win/webauthn_api.cc b/chromium/device/fido/win/webauthn_api.cc index 5f17312cde0..4706be4ec8c 100644 --- a/chromium/device/fido/win/webauthn_api.cc +++ b/chromium/device/fido/win/webauthn_api.cc @@ -295,13 +295,6 @@ AuthenticatorMakeCredentialBlocking(WinWebAuthnApi* webauthn_api, }; WEBAUTHN_CREDENTIAL_ATTESTATION* credential_attestation = nullptr; - std::unique_ptr<WEBAUTHN_CREDENTIAL_ATTESTATION, - std::function<void(PWEBAUTHN_CREDENTIAL_ATTESTATION)>> - credential_attestation_deleter( - credential_attestation, - [webauthn_api](PWEBAUTHN_CREDENTIAL_ATTESTATION ptr) { - webauthn_api->FreeCredentialAttestation(ptr); - }); FIDO_LOG(DEBUG) << "WebAuthNAuthenticatorMakeCredential(" << "rp=" << rp_info << ", user=" << user_info @@ -312,6 +305,14 @@ AuthenticatorMakeCredentialBlocking(WinWebAuthnApi* webauthn_api, HRESULT hresult = webauthn_api->AuthenticatorMakeCredential( h_wnd, &rp_info, &user_info, &cose_credential_parameters, &client_data, &options, &credential_attestation); + std::unique_ptr<WEBAUTHN_CREDENTIAL_ATTESTATION, + std::function<void(PWEBAUTHN_CREDENTIAL_ATTESTATION)>> + credential_attestation_deleter( + credential_attestation, + [webauthn_api](PWEBAUTHN_CREDENTIAL_ATTESTATION ptr) { + webauthn_api->FreeCredentialAttestation(ptr); + }); + if (hresult != S_OK) { FIDO_LOG(DEBUG) << "WebAuthNAuthenticatorMakeCredential()=" << webauthn_api->GetErrorName(hresult); @@ -403,16 +404,17 @@ AuthenticatorGetAssertionBlocking(WinWebAuthnApi* webauthn_api, }; WEBAUTHN_ASSERTION* assertion = nullptr; - std::unique_ptr<WEBAUTHN_ASSERTION, std::function<void(PWEBAUTHN_ASSERTION)>> - assertion_deleter(assertion, [webauthn_api](PWEBAUTHN_ASSERTION ptr) { - webauthn_api->FreeAssertion(ptr); - }); FIDO_LOG(DEBUG) << "WebAuthNAuthenticatorGetAssertion(" << "rp_id=\"" << rp_id16 << "\", client_data=" << client_data << ", options=" << options << ")"; HRESULT hresult = webauthn_api->AuthenticatorGetAssertion( h_wnd, base::as_wcstr(rp_id16), &client_data, &options, &assertion); + std::unique_ptr<WEBAUTHN_ASSERTION, std::function<void(PWEBAUTHN_ASSERTION)>> + assertion_deleter(assertion, [webauthn_api](PWEBAUTHN_ASSERTION ptr) { + webauthn_api->FreeAssertion(ptr); + }); + if (hresult != S_OK) { FIDO_LOG(DEBUG) << "WebAuthNAuthenticatorGetAssertion()=" << webauthn_api->GetErrorName(hresult); diff --git a/chromium/device/gamepad/gamepad_device_linux.cc b/chromium/device/gamepad/gamepad_device_linux.cc index f188c2a69e8..ea02ce18f51 100644 --- a/chromium/device/gamepad/gamepad_device_linux.cc +++ b/chromium/device/gamepad/gamepad_device_linux.cc @@ -197,6 +197,40 @@ uint16_t HexStringToUInt16WithDefault(base::StringPiece input, return static_cast<uint16_t>(out); } +#if defined(OS_CHROMEOS) +void OnOpenPathSuccess( + chromeos::PermissionBrokerClient::OpenPathCallback callback, + scoped_refptr<base::SequencedTaskRunner> polling_runner, + base::ScopedFD fd) { + polling_runner->PostTask(FROM_HERE, + base::BindOnce(std::move(callback), std::move(fd))); +} + +void OnOpenPathError( + chromeos::PermissionBrokerClient::OpenPathCallback callback, + scoped_refptr<base::SequencedTaskRunner> polling_runner, + const std::string& error_name, + const std::string& error_message) { + polling_runner->PostTask( + FROM_HERE, base::BindOnce(std::move(callback), base::ScopedFD())); +} + +void OpenPathWithPermissionBroker( + const std::string& path, + chromeos::PermissionBrokerClient::OpenPathCallback callback, + scoped_refptr<base::SequencedTaskRunner> polling_runner) { + auto* client = chromeos::PermissionBrokerClient::Get(); + DCHECK(client) << "Could not get permission broker client."; + auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback)); + auto success_callback = + base::BindOnce(&OnOpenPathSuccess, copyable_callback, polling_runner); + auto error_callback = + base::BindOnce(&OnOpenPathError, copyable_callback, polling_runner); + client->OpenPath(path, std::move(success_callback), + std::move(error_callback)); +} +#endif // defined(OS_CHROMEOS) + } // namespace GamepadDeviceLinux::GamepadDeviceLinux( @@ -250,6 +284,12 @@ void GamepadDeviceLinux::ReadPadState(Gamepad* pad) { pad_updated = true; } + // Mark used buttons. + for (size_t button_index = 0; button_index < Gamepad::kButtonsLengthCap; + ++button_index) { + pad->buttons[button_index].used = button_indices_used_[button_index]; + } + if (pad_updated) pad->timestamp = GamepadDataFetcher::CurrentTimeInMicroseconds(); } @@ -272,6 +312,7 @@ bool GamepadDeviceLinux::ReadJoydevState(Gamepad* pad) { continue; pad->axes[item] = event.value / kMaxLinuxAxisValue; + pad->axes_used |= 1 << item; if (item >= pad->axes_length) pad->axes_length = item + 1; @@ -280,6 +321,7 @@ bool GamepadDeviceLinux::ReadJoydevState(Gamepad* pad) { if (item >= Gamepad::kButtonsLengthCap) continue; + pad->buttons[item].used = true; pad->buttons[item].pressed = event.value; pad->buttons[item].value = event.value ? 1.0 : 0.0; @@ -530,9 +572,8 @@ void GamepadDeviceLinux::OpenHidrawNode(const UdevGamepadLinux& pad_info, weak_factory_.GetWeakPtr(), std::move(callback)); dbus_runner_->PostTask( FROM_HERE, - base::BindOnce(&GamepadDeviceLinux::OpenPathWithPermissionBroker, - weak_factory_.GetWeakPtr(), pad_info.path, - std::move(open_path_callback))); + base::BindOnce(&OpenPathWithPermissionBroker, pad_info.path, + std::move(open_path_callback), polling_runner_)); return; } #endif // defined(OS_CHROMEOS) @@ -601,40 +642,6 @@ void GamepadDeviceLinux::CloseHidrawNode() { hidraw_fd_.reset(); } -#if defined(OS_CHROMEOS) -void GamepadDeviceLinux::OpenPathWithPermissionBroker( - const std::string& path, - OpenPathCallback callback) { - DCHECK(dbus_runner_->RunsTasksInCurrentSequence()); - auto* client = chromeos::PermissionBrokerClient::Get(); - DCHECK(client) << "Could not get permission broker client."; - auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback)); - auto success_callback = - base::BindOnce(&GamepadDeviceLinux::OnOpenPathSuccess, - weak_factory_.GetWeakPtr(), copyable_callback); - auto error_callback = - base::BindOnce(&GamepadDeviceLinux::OnOpenPathError, - weak_factory_.GetWeakPtr(), copyable_callback); - client->OpenPath(path, std::move(success_callback), - std::move(error_callback)); -} - -void GamepadDeviceLinux::OnOpenPathSuccess(OpenPathCallback callback, - base::ScopedFD fd) { - DCHECK(dbus_runner_->RunsTasksInCurrentSequence()); - polling_runner_->PostTask(FROM_HERE, - base::BindOnce(std::move(callback), std::move(fd))); -} - -void GamepadDeviceLinux::OnOpenPathError(OpenPathCallback callback, - const std::string& error_name, - const std::string& error_message) { - DCHECK(dbus_runner_->RunsTasksInCurrentSequence()); - polling_runner_->PostTask( - FROM_HERE, base::BindOnce(std::move(callback), base::ScopedFD())); -} -#endif - void GamepadDeviceLinux::SetVibration(double strong_magnitude, double weak_magnitude) { DCHECK(polling_runner_->RunsTasksInCurrentSequence()); @@ -696,6 +703,7 @@ void GamepadDeviceLinux::SetZeroVibration() { } base::WeakPtr<AbstractHapticGamepad> GamepadDeviceLinux::GetWeakPtr() { + DCHECK(polling_runner_->RunsTasksInCurrentSequence()); return weak_factory_.GetWeakPtr(); } diff --git a/chromium/device/gamepad/gamepad_device_linux.h b/chromium/device/gamepad/gamepad_device_linux.h index 7b4f25e4398..3ef9a0ccdea 100644 --- a/chromium/device/gamepad/gamepad_device_linux.h +++ b/chromium/device/gamepad/gamepad_device_linux.h @@ -99,8 +99,6 @@ class GamepadDeviceLinux final : public AbstractHapticGamepad { base::WeakPtr<AbstractHapticGamepad> GetWeakPtr() override; private: - using OpenPathCallback = base::OnceCallback<void(base::ScopedFD)>; - // AbstractHapticGamepad private implementation. void DoShutdown() override; @@ -108,15 +106,6 @@ class GamepadDeviceLinux final : public AbstractHapticGamepad { base::ScopedFD fd); void InitializeHidraw(base::ScopedFD fd); -#if defined(OS_CHROMEOS) - void OpenPathWithPermissionBroker(const std::string& path, - OpenPathCallback callback); - void OnOpenPathSuccess(OpenPathCallback callback, base::ScopedFD fd); - void OnOpenPathError(OpenPathCallback callback, - const std::string& error_name, - const std::string& error_message); -#endif - // The syspath prefix is used to identify device nodes that refer to the same // underlying gamepad through different interfaces. // @@ -206,6 +195,7 @@ class GamepadDeviceLinux final : public AbstractHapticGamepad { // Task runner to use for gamepad polling. scoped_refptr<base::SequencedTaskRunner> polling_runner_; + // Weak pointer factory for use only on the |polling_runner_| thread. base::WeakPtrFactory<GamepadDeviceLinux> weak_factory_{this}; }; diff --git a/chromium/device/gamepad/gamepad_device_mac.mm b/chromium/device/gamepad/gamepad_device_mac.mm index 90802da1eda..611a301ecc1 100644 --- a/chromium/device/gamepad/gamepad_device_mac.mm +++ b/chromium/device/gamepad/gamepad_device_mac.mm @@ -208,6 +208,7 @@ bool GamepadDeviceMac::AddButtons(Gamepad* gamepad) { continue; button_elements_[button_index] = element; + gamepad->buttons[button_index].used = true; button_count = std::max(button_count, button_index + 1); } else { // Check for common gamepad buttons that are not on the Button usage @@ -241,6 +242,7 @@ bool GamepadDeviceMac::AddButtons(Gamepad* gamepad) { break; button_elements_[button_index] = special_element[special_index]; + gamepad->buttons[button_index].used = true; button_count = std::max(button_count, button_index + 1); if (--unmapped_button_count == 0) @@ -351,6 +353,8 @@ bool GamepadDeviceMac::AddAxes(Gamepad* gamepad) { axis_minimums_[axis_index] = axis_min; axis_maximums_[axis_index] = axis_max; axis_report_sizes_[axis_index] = IOHIDElementGetReportSize(element); + + gamepad->axes_used |= 1 << axis_index; } } diff --git a/chromium/device/gamepad/gamepad_id_list.cc b/chromium/device/gamepad/gamepad_id_list.cc index 831bc8e40b9..14accc69dec 100644 --- a/chromium/device/gamepad/gamepad_id_list.cc +++ b/chromium/device/gamepad/gamepad_id_list.cc @@ -142,6 +142,9 @@ constexpr struct GamepadInfo { // Elecom Co., Ltd {0x056e, 0x2003, kXInputTypeNone}, {0x056e, 0x2004, kXInputTypeXbox360}, + {0x056e, 0x200f, kXInputTypeNone}, + {0x056e, 0x2010, kXInputTypeNone}, + {0x056e, 0x2013, kXInputTypeXbox360}, // Nintendo Co., Ltd {0x057e, 0x0306, kXInputTypeNone}, {0x057e, 0x0330, kXInputTypeNone}, @@ -371,6 +374,7 @@ constexpr struct GamepadInfo { {0x0f0d, 0x008a, kXInputTypeNone}, {0x0f0d, 0x008b, kXInputTypeNone}, {0x0f0d, 0x0090, kXInputTypeNone}, + {0x0f0d, 0x00c1, kXInputTypeNone}, {0x0f0d, 0x00ee, kXInputTypeNone}, // Jess Technology Co., Ltd {0x0f30, 0x010b, kXInputTypeXbox}, @@ -649,9 +653,16 @@ GamepadId GamepadIdList::GetGamepadId(base::StringPiece product_name, uint16_t vendor_id, uint16_t product_id) const { const auto* entry = GetGamepadInfo(vendor_id, product_id); - // The ID value combines the vendor and product IDs. - return entry ? static_cast<GamepadId>((vendor_id << 16) | product_id) - : GamepadId::kUnknownGamepad; + if (entry) { + // The ID value combines the vendor and product IDs. + return static_cast<GamepadId>((vendor_id << 16) | product_id); + } + // Special cases for devices which don't report a valid vendor ID. + if (vendor_id == 0x0 && product_id == 0x0 && + product_name == "Lic Pro Controller") { + return GamepadId::kPowerALicPro; + } + return GamepadId::kUnknownGamepad; } std::vector<std::tuple<uint16_t, uint16_t, XInputType>> diff --git a/chromium/device/gamepad/gamepad_id_list.h b/chromium/device/gamepad/gamepad_id_list.h index ca1ee7feba3..0bde5a720c9 100644 --- a/chromium/device/gamepad/gamepad_id_list.h +++ b/chromium/device/gamepad/gamepad_id_list.h @@ -33,15 +33,19 @@ enum XInputType { enum class GamepadId : uint32_t { // ID value representing an unknown gamepad or non-gamepad. kUnknownGamepad = 0, - + // Fake IDs for devices which report as 0x0000 0x0000 + kPowerALicPro = 0x0000ff00, // ID values for supported devices. kAsusTekProduct4500 = 0x0b054500, kBroadcomProduct8502 = 0x0a5c8502, kDragonRiseProduct0006 = 0x00790006, kDragonRiseProduct0011 = 0x00790011, + kElecomProduct200f = 0x056e200f, + kElecomProduct2010 = 0x056e2010, kGoogleProduct2c40 = 0x18d12c40, kGoogleProduct9400 = 0x18d19400, kGreenAsiaProduct0003 = 0x0e8f0003, + kHoriProduct00c1 = 0x0f0d00c1, kLakeviewResearchProduct0005 = 0x09250005, kLakeviewResearchProduct8866 = 0x09258866, kLogitechProductc216 = 0x046dc216, diff --git a/chromium/device/gamepad/gamepad_platform_data_fetcher_android.cc b/chromium/device/gamepad/gamepad_platform_data_fetcher_android.cc index 38a6cb45240..0d16efd0222 100644 --- a/chromium/device/gamepad/gamepad_platform_data_fetcher_android.cc +++ b/chromium/device/gamepad/gamepad_platform_data_fetcher_android.cc @@ -121,15 +121,17 @@ static void JNI_GamepadList_SetGamepadData( std::vector<float> buttons; base::android::JavaFloatArrayToFloatVector(env, jbuttons, &buttons); - // Set Gamepad buttonslength to total number of axes on the gamepad - // device. Only return the first buttonsLengthCap if axeslength captured by + // Set Gamepad buttonslength to total number of buttons on the gamepad + // device. Only return the first buttonsLengthCap if buttonslength captured by // GamepadList is larger than buttonsLengthCap. pad.buttons_length = std::min(static_cast<int>(buttons.size()), static_cast<int>(Gamepad::kButtonsLengthCap)); // Copy buttons state to the Gamepad buttons[]. for (unsigned int j = 0; j < pad.buttons_length; j++) { - pad.buttons[j].pressed = buttons[j]; + pad.buttons[j].pressed = + buttons[j] > GamepadButton::kDefaultButtonPressedThreshold; + pad.buttons[j].touched = buttons[j] > 0.0f; pad.buttons[j].value = buttons[j]; } } diff --git a/chromium/device/gamepad/gamepad_standard_mappings.cc b/chromium/device/gamepad/gamepad_standard_mappings.cc index e30498514cb..94645a0fbfa 100644 --- a/chromium/device/gamepad/gamepad_standard_mappings.cc +++ b/chromium/device/gamepad/gamepad_standard_mappings.cc @@ -6,25 +6,31 @@ namespace device { -GamepadButton AxisToButton(float input) { - float value = (input + 1.f) / 2.f; +namespace { + +const float kButtonAxisDeadzone = 0.01f; + +GamepadButton ValueToButton(float value) { bool pressed = value > GamepadButton::kDefaultButtonPressedThreshold; - bool touched = value > 0.0f; + bool touched = value > 0.f; return GamepadButton(pressed, touched, value); } +} // namespace + +GamepadButton AxisToButton(float input) { + float value = (input + 1.f) / 2.f; + return ValueToButton(value); +} + GamepadButton AxisNegativeAsButton(float input) { - float value = (input < -0.5f) ? 1.f : 0.f; - bool pressed = value > GamepadButton::kDefaultButtonPressedThreshold; - bool touched = value > 0.0f; - return GamepadButton(pressed, touched, value); + float value = input < -kButtonAxisDeadzone ? -input : 0.f; + return ValueToButton(value); } GamepadButton AxisPositiveAsButton(float input) { - float value = (input > 0.5f) ? 1.f : 0.f; - bool pressed = value > GamepadButton::kDefaultButtonPressedThreshold; - bool touched = value > 0.0f; - return GamepadButton(pressed, touched, value); + float value = input > kButtonAxisDeadzone ? input : 0.f; + return ValueToButton(value); } GamepadButton ButtonFromButtonAndAxis(GamepadButton button, float axis) { @@ -33,7 +39,7 @@ GamepadButton ButtonFromButtonAndAxis(GamepadButton button, float axis) { } GamepadButton NullButton() { - return GamepadButton(false, false, 0.0); + return GamepadButton(); } void DpadFromAxis(Gamepad* mapped, float dir) { diff --git a/chromium/device/gamepad/gamepad_standard_mappings_linux.cc b/chromium/device/gamepad/gamepad_standard_mappings_linux.cc index bf685f48653..d438c3d93bf 100644 --- a/chromium/device/gamepad/gamepad_standard_mappings_linux.cc +++ b/chromium/device/gamepad/gamepad_standard_mappings_linux.cc @@ -35,6 +35,13 @@ enum StadiaGamepadButtons { STADIA_GAMEPAD_BUTTON_COUNT }; +// The Switch Pro controller has a Capture button that has no equivalent in the +// Standard Gamepad. +enum SwitchProButtons { + SWITCH_PRO_BUTTON_CAPTURE = BUTTON_INDEX_COUNT, + SWITCH_PRO_BUTTON_COUNT +}; + void MapperXInputStyleGamepad(const Gamepad& input, Gamepad* mapped) { *mapped = input; mapped->buttons[BUTTON_INDEX_LEFT_TRIGGER] = AxisToButton(input.axes[2]); @@ -613,11 +620,8 @@ void MapperSwitchJoyCon(const Gamepad& input, Gamepad* mapped) { } void MapperSwitchPro(const Gamepad& input, Gamepad* mapped) { - // The Switch Pro controller has a Capture button that has no equivalent in - // the Standard Gamepad. - const size_t kSwitchProExtraButtonCount = 1; *mapped = input; - mapped->buttons_length = BUTTON_INDEX_COUNT + kSwitchProExtraButtonCount; + mapped->buttons_length = SWITCH_PRO_BUTTON_COUNT; mapped->axes_length = AXIS_INDEX_COUNT; } @@ -768,7 +772,18 @@ void MapperSnakebyteIDroidCon(const Gamepad& input, Gamepad* mapped) { mapped->buttons[BUTTON_INDEX_RIGHT_THUMBSTICK] = input.buttons[5]; mapped->buttons[BUTTON_INDEX_META] = NullButton(); - if (input.axes_length == 7) { + if ((input.axes_used & 0b1000000) == 0) { + // "Game controller 1" mode: digital triggers. + mapped->buttons[BUTTON_INDEX_LEFT_TRIGGER] = input.buttons[8]; + mapped->buttons[BUTTON_INDEX_RIGHT_TRIGGER] = input.buttons[9]; + mapped->buttons[BUTTON_INDEX_DPAD_UP] = AxisNegativeAsButton(input.axes[5]); + mapped->buttons[BUTTON_INDEX_DPAD_DOWN] = + AxisPositiveAsButton(input.axes[5]); + mapped->buttons[BUTTON_INDEX_DPAD_LEFT] = + AxisNegativeAsButton(input.axes[4]); + mapped->buttons[BUTTON_INDEX_DPAD_RIGHT] = + AxisPositiveAsButton(input.axes[4]); + } else { // "Game controller 2" mode: analog triggers. mapped->buttons[BUTTON_INDEX_LEFT_TRIGGER] = AxisPositiveAsButton(input.axes[2]); @@ -781,31 +796,67 @@ void MapperSnakebyteIDroidCon(const Gamepad& input, Gamepad* mapped) { AxisNegativeAsButton(input.axes[5]); mapped->buttons[BUTTON_INDEX_DPAD_RIGHT] = AxisPositiveAsButton(input.axes[5]); - mapped->buttons[BUTTON_INDEX_META] = NullButton(); mapped->axes[AXIS_INDEX_RIGHT_STICK_X] = input.axes[3]; mapped->axes[AXIS_INDEX_RIGHT_STICK_Y] = input.axes[4]; - } else { - // "Game controller 1" mode: digital triggers. - mapped->buttons[BUTTON_INDEX_LEFT_TRIGGER] = input.buttons[8]; - mapped->buttons[BUTTON_INDEX_RIGHT_TRIGGER] = input.buttons[9]; - mapped->buttons[BUTTON_INDEX_DPAD_UP] = AxisNegativeAsButton(input.axes[5]); - mapped->buttons[BUTTON_INDEX_DPAD_DOWN] = - AxisPositiveAsButton(input.axes[5]); - mapped->buttons[BUTTON_INDEX_DPAD_LEFT] = - AxisNegativeAsButton(input.axes[4]); - mapped->buttons[BUTTON_INDEX_DPAD_RIGHT] = - AxisPositiveAsButton(input.axes[4]); } + mapped->buttons_length = BUTTON_INDEX_COUNT - 1; // no meta mapped->axes_length = AXIS_INDEX_COUNT; } +void MapperHoripadSwitch(const Gamepad& input, Gamepad* mapped) { + *mapped = input; + mapped->buttons[BUTTON_INDEX_PRIMARY] = input.buttons[1]; + mapped->buttons[BUTTON_INDEX_SECONDARY] = input.buttons[2]; + mapped->buttons[BUTTON_INDEX_TERTIARY] = input.buttons[0]; + mapped->buttons[BUTTON_INDEX_DPAD_UP] = AxisNegativeAsButton(input.axes[5]); + mapped->buttons[BUTTON_INDEX_DPAD_DOWN] = AxisPositiveAsButton(input.axes[5]); + mapped->buttons[BUTTON_INDEX_DPAD_LEFT] = AxisNegativeAsButton(input.axes[4]); + mapped->buttons[BUTTON_INDEX_DPAD_RIGHT] = + AxisPositiveAsButton(input.axes[4]); + mapped->buttons[BUTTON_INDEX_META] = input.buttons[12]; + mapped->buttons[SWITCH_PRO_BUTTON_CAPTURE] = input.buttons[13]; + mapped->buttons_length = SWITCH_PRO_BUTTON_COUNT; + mapped->axes_length = AXIS_INDEX_COUNT; +} + +void MapperElecomWiredDirectInput(const Gamepad& input, Gamepad* mapped) { + *mapped = input; + mapped->buttons[BUTTON_INDEX_PRIMARY] = input.buttons[2]; + mapped->buttons[BUTTON_INDEX_SECONDARY] = input.buttons[3]; + mapped->buttons[BUTTON_INDEX_TERTIARY] = input.buttons[0]; + mapped->buttons[BUTTON_INDEX_QUATERNARY] = input.buttons[1]; + mapped->buttons[BUTTON_INDEX_DPAD_UP] = AxisNegativeAsButton(input.axes[5]); + mapped->buttons[BUTTON_INDEX_DPAD_DOWN] = AxisPositiveAsButton(input.axes[5]); + mapped->buttons[BUTTON_INDEX_DPAD_LEFT] = AxisNegativeAsButton(input.axes[4]); + mapped->buttons[BUTTON_INDEX_DPAD_RIGHT] = + AxisPositiveAsButton(input.axes[4]); + mapped->buttons[BUTTON_INDEX_BACK_SELECT] = input.buttons[10]; + mapped->buttons[BUTTON_INDEX_START] = input.buttons[11]; + mapped->buttons[BUTTON_INDEX_LEFT_THUMBSTICK] = input.buttons[8]; + mapped->buttons[BUTTON_INDEX_RIGHT_THUMBSTICK] = input.buttons[9]; + mapped->buttons[BUTTON_INDEX_META] = input.buttons[12]; + mapped->buttons_length = BUTTON_INDEX_COUNT; + mapped->axes_length = AXIS_INDEX_COUNT; +} + +void MapperElecomWirelessDirectInput(const Gamepad& input, Gamepad* mapped) { + MapperElecomWiredDirectInput(input, mapped); + + mapped->axes[AXIS_INDEX_RIGHT_STICK_X] = input.axes[3]; + mapped->axes[AXIS_INDEX_RIGHT_STICK_Y] = input.axes[2]; +} + constexpr struct MappingData { GamepadId gamepad_id; GamepadStandardMappingFunction function; } AvailableMappings[] = { + // PowerA Wireless Controller - Nintendo GameCube style + {GamepadId::kPowerALicPro, MapperSwitchPro}, // DragonRise Generic USB {GamepadId::kDragonRiseProduct0006, MapperDragonRiseGeneric}, + // HORIPAD for Nintendo Switch + {GamepadId::kHoriProduct00c1, MapperHoripadSwitch}, // Xbox One S (Bluetooth) {GamepadId::kMicrosoftProduct02e0, MapperXboxOneS}, // Xbox One S (Bluetooth) @@ -882,6 +933,10 @@ constexpr struct MappingData { {GamepadId::kPrototypeVendorProduct9401, MapperStadiaControllerOldFirmware}, // Snakebyte iDroid:con {GamepadId::kBroadcomProduct8502, MapperSnakebyteIDroidCon}, + // Elecom JC-U4013SBK (DirectInput mode) + {GamepadId::kElecomProduct200f, MapperElecomWiredDirectInput}, + // Elecom JC-U4113SBK (DirectInput mode) + {GamepadId::kElecomProduct2010, MapperElecomWirelessDirectInput}, }; } // namespace diff --git a/chromium/device/gamepad/gamepad_standard_mappings_mac.mm b/chromium/device/gamepad/gamepad_standard_mappings_mac.mm index d42d6a7ccca..7d068935155 100644 --- a/chromium/device/gamepad/gamepad_standard_mappings_mac.mm +++ b/chromium/device/gamepad/gamepad_standard_mappings_mac.mm @@ -26,6 +26,13 @@ enum StadiaGamepadButtons { STADIA_GAMEPAD_BUTTON_COUNT }; +// The Switch Pro controller has a Capture button that has no equivalent in the +// Standard Gamepad. +enum SwitchProButtons { + SWITCH_PRO_BUTTON_CAPTURE = BUTTON_INDEX_COUNT, + SWITCH_PRO_BUTTON_COUNT +}; + void MapperXbox360Gamepad(const Gamepad& input, Gamepad* mapped) { *mapped = input; mapped->buttons[BUTTON_INDEX_LEFT_TRIGGER] = AxisToButton(input.axes[2]); @@ -536,11 +543,8 @@ void MapperSwitchJoyCon(const Gamepad& input, Gamepad* mapped) { } void MapperSwitchPro(const Gamepad& input, Gamepad* mapped) { - // The Switch Pro controller has a Capture button that has no equivalent in - // the Standard Gamepad. - const size_t kSwitchProExtraButtonCount = 1; *mapped = input; - mapped->buttons_length = BUTTON_INDEX_COUNT + kSwitchProExtraButtonCount; + mapped->buttons_length = SWITCH_PRO_BUTTON_COUNT; mapped->axes_length = AXIS_INDEX_COUNT; } @@ -583,12 +587,68 @@ void MapperXboxOneBluetooth(const Gamepad& input, Gamepad* mapped) { mapped->axes_length = AXIS_INDEX_COUNT; } +void MapperSnakebyteIDroidCon(const Gamepad& input, Gamepad* mapped) { + *mapped = input; + mapped->buttons[BUTTON_INDEX_TERTIARY] = input.buttons[3]; + mapped->buttons[BUTTON_INDEX_QUATERNARY] = input.buttons[4]; + mapped->buttons[BUTTON_INDEX_LEFT_SHOULDER] = input.buttons[6]; + mapped->buttons[BUTTON_INDEX_RIGHT_SHOULDER] = input.buttons[7]; + mapped->buttons[BUTTON_INDEX_BACK_SELECT] = input.buttons[10]; + mapped->buttons[BUTTON_INDEX_START] = input.buttons[11]; + mapped->buttons[BUTTON_INDEX_LEFT_THUMBSTICK] = input.buttons[2]; + mapped->buttons[BUTTON_INDEX_RIGHT_THUMBSTICK] = input.buttons[5]; + mapped->buttons[BUTTON_INDEX_META] = NullButton(); + DpadFromAxis(mapped, input.axes[9]); + + // The iDroid:con has two different modes. Distinguish them based on which + // axes are used. + if ((input.axes_used & 0b11000) == 0) { + // "Game controller 1" mode: digital triggers. + mapped->buttons[BUTTON_INDEX_LEFT_TRIGGER] = input.buttons[8]; + mapped->buttons[BUTTON_INDEX_RIGHT_TRIGGER] = input.buttons[9]; + mapped->axes[AXIS_INDEX_RIGHT_STICK_Y] = input.axes[5]; + } else { + // "Game controller 2" mode: analog triggers. + mapped->buttons[BUTTON_INDEX_LEFT_TRIGGER] = + AxisPositiveAsButton(input.axes[2]); + mapped->buttons[BUTTON_INDEX_RIGHT_TRIGGER] = + AxisNegativeAsButton(input.axes[2]); + mapped->axes[AXIS_INDEX_RIGHT_STICK_X] = input.axes[3]; + mapped->axes[AXIS_INDEX_RIGHT_STICK_Y] = input.axes[4]; + } + + mapped->buttons_length = BUTTON_INDEX_COUNT - 1; // no meta + mapped->axes_length = AXIS_INDEX_COUNT; +} + +void MapperHoripadSwitch(const Gamepad& input, Gamepad* mapped) { + *mapped = input; + mapped->buttons[BUTTON_INDEX_PRIMARY] = input.buttons[1]; + mapped->buttons[BUTTON_INDEX_SECONDARY] = input.buttons[2]; + mapped->buttons[BUTTON_INDEX_TERTIARY] = input.buttons[0]; + DpadFromAxis(mapped, input.axes[9]); + mapped->buttons[BUTTON_INDEX_META] = input.buttons[12]; + mapped->buttons[SWITCH_PRO_BUTTON_CAPTURE] = input.buttons[13]; + mapped->axes[AXIS_INDEX_LEFT_STICK_X] = input.axes[0]; + mapped->axes[AXIS_INDEX_LEFT_STICK_Y] = input.axes[1]; + mapped->axes[AXIS_INDEX_RIGHT_STICK_X] = input.axes[2]; + mapped->axes[AXIS_INDEX_RIGHT_STICK_Y] = input.axes[5]; + mapped->buttons_length = SWITCH_PRO_BUTTON_COUNT; + mapped->axes_length = AXIS_INDEX_COUNT; +} + constexpr struct MappingData { GamepadId gamepad_id; GamepadStandardMappingFunction function; } AvailableMappings[] = { + // PowerA Wireless Controller - Nintendo GameCube style + {GamepadId::kPowerALicPro, MapperSwitchPro}, + // Snakebyte iDroid:con + {GamepadId::kBroadcomProduct8502, MapperSnakebyteIDroidCon}, // DragonRise Generic USB {GamepadId::kDragonRiseProduct0006, MapperDragonRiseGeneric}, + // HORIPAD for Nintendo Switch + {GamepadId::kHoriProduct00c1, MapperHoripadSwitch}, // Xbox 360 Wired {GamepadId::kMicrosoftProduct028e, MapperXbox360Gamepad}, // Xbox 360 Wireless diff --git a/chromium/device/gamepad/gamepad_standard_mappings_win.cc b/chromium/device/gamepad/gamepad_standard_mappings_win.cc index aed6ca57a25..a4f48b2f7b6 100644 --- a/chromium/device/gamepad/gamepad_standard_mappings_win.cc +++ b/chromium/device/gamepad/gamepad_standard_mappings_win.cc @@ -26,6 +26,13 @@ enum StadiaGamepadButtons { STADIA_GAMEPAD_BUTTON_COUNT }; +// The Switch Pro controller has a Capture button that has no equivalent in the +// Standard Gamepad. +enum SwitchProButtons { + SWITCH_PRO_BUTTON_CAPTURE = BUTTON_INDEX_COUNT, + SWITCH_PRO_BUTTON_COUNT +}; + void MapperLogitechDInput(const Gamepad& input, Gamepad* mapped) { *mapped = input; mapped->buttons[BUTTON_INDEX_PRIMARY] = input.buttons[1]; @@ -53,11 +60,11 @@ void Mapper2Axes8Keys(const Gamepad& input, Gamepad* mapped) { AxisPositiveAsButton(input.axes[0]); // Missing buttons - mapped->buttons[BUTTON_INDEX_LEFT_TRIGGER] = GamepadButton(); - mapped->buttons[BUTTON_INDEX_RIGHT_TRIGGER] = GamepadButton(); - mapped->buttons[BUTTON_INDEX_LEFT_THUMBSTICK] = GamepadButton(); - mapped->buttons[BUTTON_INDEX_RIGHT_THUMBSTICK] = GamepadButton(); - mapped->buttons[BUTTON_INDEX_META] = GamepadButton(); + mapped->buttons[BUTTON_INDEX_LEFT_TRIGGER] = NullButton(); + mapped->buttons[BUTTON_INDEX_RIGHT_TRIGGER] = NullButton(); + mapped->buttons[BUTTON_INDEX_LEFT_THUMBSTICK] = NullButton(); + mapped->buttons[BUTTON_INDEX_RIGHT_THUMBSTICK] = NullButton(); + mapped->buttons[BUTTON_INDEX_META] = NullButton(); mapped->buttons_length = BUTTON_INDEX_COUNT - 1; mapped->axes_length = 0; @@ -392,11 +399,8 @@ void MapperSwitchJoyCon(const Gamepad& input, Gamepad* mapped) { } void MapperSwitchPro(const Gamepad& input, Gamepad* mapped) { - // The Switch Pro controller has a Capture button that has no equivalent in - // the Standard Gamepad. - const size_t kSwitchProExtraButtonCount = 1; *mapped = input; - mapped->buttons_length = BUTTON_INDEX_COUNT + kSwitchProExtraButtonCount; + mapped->buttons_length = SWITCH_PRO_BUTTON_COUNT; mapped->axes_length = AXIS_INDEX_COUNT; } @@ -416,12 +420,68 @@ void MapperSwitchComposite(const Gamepad& input, Gamepad* mapped) { mapped->axes_length = AXIS_INDEX_COUNT; } +void MapperSnakebyteIDroidCon(const Gamepad& input, Gamepad* mapped) { + *mapped = input; + mapped->buttons[BUTTON_INDEX_TERTIARY] = input.buttons[3]; + mapped->buttons[BUTTON_INDEX_QUATERNARY] = input.buttons[4]; + mapped->buttons[BUTTON_INDEX_LEFT_SHOULDER] = input.buttons[6]; + mapped->buttons[BUTTON_INDEX_RIGHT_SHOULDER] = input.buttons[7]; + mapped->buttons[BUTTON_INDEX_BACK_SELECT] = input.buttons[10]; + mapped->buttons[BUTTON_INDEX_START] = input.buttons[11]; + mapped->buttons[BUTTON_INDEX_LEFT_THUMBSTICK] = input.buttons[2]; + mapped->buttons[BUTTON_INDEX_RIGHT_THUMBSTICK] = input.buttons[5]; + mapped->buttons[BUTTON_INDEX_META] = NullButton(); + DpadFromAxis(mapped, input.axes[9]); + + // The iDroid:con has two different modes. Distinguish them based on which + // axes are used. + if ((input.axes_used & 0b11000) == 0) { + // "Game controller 1" mode: digital triggers. + mapped->buttons[BUTTON_INDEX_LEFT_TRIGGER] = input.buttons[8]; + mapped->buttons[BUTTON_INDEX_RIGHT_TRIGGER] = input.buttons[9]; + mapped->axes[AXIS_INDEX_RIGHT_STICK_Y] = input.axes[5]; + } else { + // "Game controller 2" mode: analog triggers. + mapped->buttons[BUTTON_INDEX_LEFT_TRIGGER] = + AxisPositiveAsButton(input.axes[2]); + mapped->buttons[BUTTON_INDEX_RIGHT_TRIGGER] = + AxisNegativeAsButton(input.axes[2]); + mapped->axes[AXIS_INDEX_RIGHT_STICK_X] = input.axes[3]; + mapped->axes[AXIS_INDEX_RIGHT_STICK_Y] = input.axes[4]; + } + + mapped->buttons_length = BUTTON_INDEX_COUNT - 1; // no meta + mapped->axes_length = AXIS_INDEX_COUNT; +} + +void MapperHoripadSwitch(const Gamepad& input, Gamepad* mapped) { + *mapped = input; + mapped->buttons[BUTTON_INDEX_PRIMARY] = input.buttons[1]; + mapped->buttons[BUTTON_INDEX_SECONDARY] = input.buttons[2]; + mapped->buttons[BUTTON_INDEX_TERTIARY] = input.buttons[0]; + DpadFromAxis(mapped, input.axes[9]); + mapped->buttons[BUTTON_INDEX_META] = input.buttons[12]; + mapped->buttons[SWITCH_PRO_BUTTON_CAPTURE] = input.buttons[13]; + mapped->axes[AXIS_INDEX_LEFT_STICK_X] = input.axes[0]; + mapped->axes[AXIS_INDEX_LEFT_STICK_Y] = input.axes[1]; + mapped->axes[AXIS_INDEX_RIGHT_STICK_X] = input.axes[2]; + mapped->axes[AXIS_INDEX_RIGHT_STICK_Y] = input.axes[5]; + mapped->buttons_length = SWITCH_PRO_BUTTON_COUNT; + mapped->axes_length = AXIS_INDEX_COUNT; +} + constexpr struct MappingData { GamepadId gamepad_id; GamepadStandardMappingFunction function; } AvailableMappings[] = { + // PowerA Wireless Controller - Nintendo GameCube style + {GamepadId::kPowerALicPro, MapperSwitchPro}, + // Snakebyte iDroid:con + {GamepadId::kBroadcomProduct8502, MapperSnakebyteIDroidCon}, // 2Axes 8Keys Game Pad {GamepadId::kDragonRiseProduct0011, Mapper2Axes8Keys}, + // HORIPAD for Nintendo Switch + {GamepadId::kHoriProduct00c1, MapperHoripadSwitch}, // Logitech F310, D-mode {GamepadId::kLogitechProductc216, MapperLogitechDInput}, // Logitech F510, D-mode diff --git a/chromium/device/gamepad/nintendo_controller.cc b/chromium/device/gamepad/nintendo_controller.cc index 662cc20c878..66c0a0c8509 100644 --- a/chromium/device/gamepad/nintendo_controller.cc +++ b/chromium/device/gamepad/nintendo_controller.cc @@ -95,6 +95,15 @@ const uint8_t kGyroPerformance208Hz = 0x01; const uint8_t kAccelerometerFilterBandwidth100Hz = 0x01; const uint8_t kPlayerLightPattern1 = 0x01; +// Bogus calibration value that should be ignored. +const uint16_t kCalBogusValue = 0xfff; + +// Default calibration values to use if the controller returns bogus values. +const uint16_t kCalDefaultDeadzone = 160; +const uint16_t kCalDefaultMin = 550; +const uint16_t kCalDefaultCenter = 2050; +const uint16_t kCalDefaultMax = 3550; + // Parameters for the "strong" and "weak" components of the dual-rumble effect. const double kVibrationFrequencyStrongRumble = 141.0; const double kVibrationFrequencyWeakRumble = 182.0; @@ -304,6 +313,11 @@ void UnpackSwitchAnalogStickParameters( DCHECK(data); // Only fetch the dead zone and range ratio. The other parameters are unknown. UnpackShorts(data[3], data[4], data[5], &cal.dead_zone, &cal.range_ratio); + if (cal.dead_zone == kCalBogusValue) { + // If the controller reports an invalid dead zone, default to something + // reasonable. + cal.dead_zone = kCalDefaultDeadzone; + } } // Unpack the IMU calibration data into |cal| @@ -349,14 +363,30 @@ void UnpackSwitchAnalogStickCalibration( UnpackShorts(data[9], data[10], data[11], &cal.rx_center, &cal.ry_center); UnpackShorts(data[12], data[13], data[14], &cal.rx_min, &cal.ry_min); UnpackShorts(data[15], data[16], data[17], &cal.rx_max, &cal.ry_max); - cal.lx_min = cal.lx_center - cal.lx_min; - cal.lx_max = cal.lx_center + cal.lx_max; - cal.ly_min = cal.ly_center - cal.ly_min; - cal.ly_max = cal.ly_center + cal.ly_max; - cal.rx_min = cal.rx_center - cal.rx_min; - cal.rx_max = cal.rx_center + cal.rx_max; - cal.ry_min = cal.ry_center - cal.ry_min; - cal.ry_max = cal.ry_center + cal.ry_max; + if (cal.lx_min == kCalBogusValue && cal.ly_max == kCalBogusValue) { + // If the controller reports bogus values, default to something reasonable. + cal.lx_min = kCalDefaultMin; + cal.lx_center = kCalDefaultCenter; + cal.lx_max = kCalDefaultMax; + cal.ly_min = kCalDefaultMin; + cal.ly_center = kCalDefaultCenter; + cal.ly_max = kCalDefaultMax; + cal.rx_min = kCalDefaultMin; + cal.rx_center = kCalDefaultCenter; + cal.rx_max = kCalDefaultMax; + cal.ry_min = kCalDefaultMin; + cal.ry_center = kCalDefaultCenter; + cal.ry_max = kCalDefaultMax; + } else { + cal.lx_min = cal.lx_center - cal.lx_min; + cal.lx_max = cal.lx_center + cal.lx_max; + cal.ly_min = cal.ly_center - cal.ly_min; + cal.ly_max = cal.ly_center + cal.ly_max; + cal.rx_min = cal.rx_center - cal.rx_min; + cal.rx_max = cal.rx_center + cal.rx_max; + cal.ry_min = cal.ry_center - cal.ry_min; + cal.ry_max = cal.ry_center + cal.ry_max; + } } // Unpack one frame of IMU data into |imu_data|. @@ -796,6 +826,9 @@ GamepadBusType BusTypeFromDeviceInfo(const mojom::HidDeviceInfo* device_info) { // Joy Cons can only be connected over Bluetooth. When connected through // a Charging Grip, the grip's ID is reported instead. return GAMEPAD_BUS_BLUETOOTH; + case GamepadId::kPowerALicPro: + // The PowerA controller can only be connected over Bluetooth. + return GAMEPAD_BUS_BLUETOOTH; default: break; } @@ -878,6 +911,7 @@ bool NintendoController::IsNintendoController(GamepadId gamepad_id) { case GamepadId::kNintendoProduct2007: case GamepadId::kNintendoProduct2009: case GamepadId::kNintendoProduct200e: + case GamepadId::kPowerALicPro: return true; default: break; @@ -918,7 +952,8 @@ GamepadHand NintendoController::GetGamepadHand() const { return GamepadHand::kNone; switch (gamepad_id_) { case GamepadId::kNintendoProduct2009: - // Switch Pro is held in both hands. + case GamepadId::kPowerALicPro: + // Switch Pro and PowerA are held in both hands. return GamepadHand::kNone; case GamepadId::kNintendoProduct2006: // Joy-Con L is held in the left hand. @@ -961,6 +996,7 @@ bool NintendoController::IsUsable() const { case GamepadId::kNintendoProduct2009: case GamepadId::kNintendoProduct2006: case GamepadId::kNintendoProduct2007: + case GamepadId::kPowerALicPro: return true; case GamepadId::kNintendoProduct200e: // Only usable as a composite device. @@ -1001,8 +1037,12 @@ void NintendoController::InitializeGamepadState(bool has_standard_mapping, Gamepad& pad) const { pad.buttons_length = SWITCH_BUTTON_INDEX_COUNT; pad.axes_length = device::AXIS_INDEX_COUNT; - pad.vibration_actuator.type = GamepadHapticActuatorType::kDualRumble; - pad.vibration_actuator.not_null = true; + if (gamepad_id_ == GamepadId::kPowerALicPro) { + pad.vibration_actuator.not_null = false; + } else { + pad.vibration_actuator.type = GamepadHapticActuatorType::kDualRumble; + pad.vibration_actuator.not_null = true; + } pad.timestamp = GamepadDataFetcher::CurrentTimeInMicroseconds(); if (is_composite_) { // Composite devices use the same product ID as the Switch Charging Grip. @@ -1407,7 +1447,13 @@ void NintendoController::ContinueInitSequence( case kPendingEnableVibration: if (spi_subcommand == kSubCommandEnableVibration) { CancelTimeout(); - MakeInitSequenceRequests(kPendingSetHomeLight); + // PowerA controller doesn't have a home light and trying to set it will + // fail, so skip this step. + if (gamepad_id_ == GamepadId::kPowerALicPro) { + MakeInitSequenceRequests(kPendingSetInputReportMode); + } else { + MakeInitSequenceRequests(kPendingSetHomeLight); + } } break; case kPendingSetHomeLight: diff --git a/chromium/device/gamepad/public/cpp/gamepad.h b/chromium/device/gamepad/public/cpp/gamepad.h index 9203c4ffbe1..3477d3d251a 100644 --- a/chromium/device/gamepad/public/cpp/gamepad.h +++ b/chromium/device/gamepad/public/cpp/gamepad.h @@ -8,6 +8,8 @@ #include <stddef.h> #include <cstdint> +#include <limits> + #include "base/component_export.h" #include "base/strings/string16.h" @@ -20,12 +22,14 @@ class GamepadButton { // Matches XInput's trigger deadzone. static constexpr float kDefaultButtonPressedThreshold = 30.f / 255.f; - GamepadButton() : pressed(false), touched(false), value(0.) {} + GamepadButton() = default; GamepadButton(bool pressed, bool touched, double value) - : pressed(pressed), touched(touched), value(value) {} - bool pressed; - bool touched; - double value; + : used(true), pressed(pressed), touched(touched), value(value) {} + // Whether the button is actually reported by the gamepad at all. + bool used{false}; + bool pressed{false}; + bool touched{false}; + double value{0.}; }; enum class GamepadHapticActuatorType { kVibration = 0, kDualRumble = 1 }; @@ -125,6 +129,14 @@ class COMPONENT_EXPORT(GAMEPAD_PUBLIC) Gamepad { // Number of valid entries in the axes array. unsigned axes_length; + // Bitfield indicating which entries of the axes array are actually used. If + // the axes index is actually used for this gamepad then the corresponding bit + // will be 1. + uint32_t axes_used; + static_assert(Gamepad::kAxesLengthCap <= + std::numeric_limits<uint32_t>::digits, + "axes_used is not large enough"); + // Normalized values representing axes, in the range [-1..1]. double axes[kAxesLengthCap]; diff --git a/chromium/device/gamepad/raw_input_gamepad_device_win.cc b/chromium/device/gamepad/raw_input_gamepad_device_win.cc index 5ba8b9f38f1..57473ca36e3 100644 --- a/chromium/device/gamepad/raw_input_gamepad_device_win.cc +++ b/chromium/device/gamepad/raw_input_gamepad_device_win.cc @@ -212,8 +212,10 @@ void RawInputGamepadDeviceWin::ReadPadState(Gamepad* pad) const { pad->timestamp = last_update_timestamp_; pad->buttons_length = buttons_length_; pad->axes_length = axes_length_; + pad->axes_used = axes_used_; for (unsigned int i = 0; i < buttons_length_; i++) { + pad->buttons[i].used = button_indices_used_[i]; pad->buttons[i].pressed = buttons_[i]; pad->buttons[i].value = buttons_[i] ? 1.0 : 0.0; } @@ -405,25 +407,18 @@ void RawInputGamepadDeviceWin::QueryButtonCapabilities(uint16_t button_count) { HidP_Input, button_caps.get(), &button_count, preparsed_data_); DCHECK_EQ(HIDP_STATUS_SUCCESS, status); - // Keep track of which button indices are in use. - std::vector<bool> button_indices_used(Gamepad::kButtonsLengthCap, false); - // Collect all inputs from the Button usage page. - QueryNormalButtonCapabilities(button_caps.get(), button_count, - &button_indices_used); + QueryNormalButtonCapabilities(button_caps.get(), button_count); // Check for common gamepad buttons that are not on the Button usage page. - QuerySpecialButtonCapabilities(button_caps.get(), button_count, - &button_indices_used); + QuerySpecialButtonCapabilities(button_caps.get(), button_count); } } void RawInputGamepadDeviceWin::QueryNormalButtonCapabilities( HIDP_BUTTON_CAPS button_caps[], - uint16_t button_count, - std::vector<bool>* button_indices_used) { + uint16_t button_count) { DCHECK(button_caps); - DCHECK(button_indices_used); // Collect all inputs from the Button usage page and assign button indices // based on the usage value. @@ -441,17 +436,15 @@ void RawInputGamepadDeviceWin::QueryNormalButtonCapabilities( std::min(Gamepad::kButtonsLengthCap - 1, button_index_max); buttons_length_ = std::max(buttons_length_, button_index_max + 1); for (size_t j = button_index_min; j <= button_index_max; ++j) - (*button_indices_used)[j] = true; + button_indices_used_[j] = true; } } } void RawInputGamepadDeviceWin::QuerySpecialButtonCapabilities( HIDP_BUTTON_CAPS button_caps[], - uint16_t button_count, - std::vector<bool>* button_indices_used) { + uint16_t button_count) { DCHECK(button_caps); - DCHECK(button_indices_used); // Check for common gamepad buttons that are not on the Button usage page. std::vector<bool> has_special_usage(kSpecialUsagesLen, false); @@ -483,19 +476,20 @@ void RawInputGamepadDeviceWin::QuerySpecialButtonCapabilities( // Advance to the next unused button index. while (button_index < Gamepad::kButtonsLengthCap && - (*button_indices_used)[button_index]) { + button_indices_used_[button_index]) { ++button_index; } if (button_index >= Gamepad::kButtonsLengthCap) break; special_button_map_[special_index] = button_index; - (*button_indices_used)[button_index] = true; + button_indices_used_[button_index] = true; ++button_index; if (--unmapped_button_count == 0) break; } + buttons_length_ = std::max(buttons_length_, button_index); } } @@ -516,6 +510,7 @@ void RawInputGamepadDeviceWin::QueryAxisCapabilities(uint16_t axis_count) { axes_[axis_index].active = true; axes_[axis_index].bitmask = GetBitmask(axes_caps[i].BitSize); axes_length_ = std::max(axes_length_, axis_index + 1); + axes_used_ |= 1 << axis_index; } else { mapped_all_axes = false; } @@ -538,6 +533,7 @@ void RawInputGamepadDeviceWin::QueryAxisCapabilities(uint16_t axis_count) { axes_[next_index].active = true; axes_[next_index].bitmask = GetBitmask(axes_caps[i].BitSize); axes_length_ = std::max(axes_length_, next_index + 1); + axes_used_ |= 1 << next_index; } } diff --git a/chromium/device/gamepad/raw_input_gamepad_device_win.h b/chromium/device/gamepad/raw_input_gamepad_device_win.h index 3c7c8999b74..60a90b15826 100644 --- a/chromium/device/gamepad/raw_input_gamepad_device_win.h +++ b/chromium/device/gamepad/raw_input_gamepad_device_win.h @@ -97,11 +97,9 @@ class RawInputGamepadDeviceWin final : public AbstractHapticGamepad { bool QueryDeviceCapabilities(); void QueryButtonCapabilities(uint16_t button_count); void QueryNormalButtonCapabilities(HIDP_BUTTON_CAPS button_caps[], - uint16_t button_count, - std::vector<bool>* button_indices_used); + uint16_t button_count); void QuerySpecialButtonCapabilities(HIDP_BUTTON_CAPS button_caps[], - uint16_t button_count, - std::vector<bool>* button_indices_used); + uint16_t button_count); void QueryAxisCapabilities(uint16_t axis_count); // True if the device described by this object is a valid RawInput gamepad. @@ -129,6 +127,15 @@ class RawInputGamepadDeviceWin final : public AbstractHapticGamepad { size_t buttons_length_ = 0; bool buttons_[Gamepad::kButtonsLengthCap]; + // Keep track of which button indices are in use. + std::vector<bool> button_indices_used_{Gamepad::kButtonsLengthCap, false}; + + // Bitfield to keep track of which axes indices are in use. + uint32_t axes_used_ = 0; + static_assert(Gamepad::kAxesLengthCap <= + std::numeric_limits<uint32_t>::digits, + "axes_used_ is not large enough"); + // Mapping from "Special" usage index (defined by the kSpecialUsages table) // to an index within the |buttons_| array, or -1 if the special usage is not // mapped for this device. diff --git a/chromium/device/udev_linux/fake_udev_loader.cc b/chromium/device/udev_linux/fake_udev_loader.cc index c825ec1b86e..885f5effdf2 100644 --- a/chromium/device/udev_linux/fake_udev_loader.cc +++ b/chromium/device/udev_linux/fake_udev_loader.cc @@ -74,6 +74,10 @@ const char* FakeUdevLoader::udev_device_get_devnode(udev_device* udev_device) { return nullptr; } +const char* FakeUdevLoader::udev_device_get_devtype(udev_device* udev_device) { + return nullptr; +} + udev_device* FakeUdevLoader::udev_device_get_parent(udev_device* udev_device) { if (!udev_device) { return nullptr; diff --git a/chromium/device/udev_linux/fake_udev_loader.h b/chromium/device/udev_linux/fake_udev_loader.h index da6d8d61f2c..8a52b091be7 100644 --- a/chromium/device/udev_linux/fake_udev_loader.h +++ b/chromium/device/udev_linux/fake_udev_loader.h @@ -31,6 +31,7 @@ class FakeUdevLoader : public device::UdevLoader { bool Init() override; const char* udev_device_get_action(udev_device* udev_device) override; const char* udev_device_get_devnode(udev_device* udev_device) override; + const char* udev_device_get_devtype(udev_device* udev_device) override; udev_device* udev_device_get_parent(udev_device* udev_device) override; udev_device* udev_device_get_parent_with_subsystem_devtype( udev_device* udev_device, diff --git a/chromium/device/udev_linux/udev.cc b/chromium/device/udev_linux/udev.cc index 2c6d27e4c4f..b27829203c2 100644 --- a/chromium/device/udev_linux/udev.cc +++ b/chromium/device/udev_linux/udev.cc @@ -27,6 +27,10 @@ const char* udev_device_get_devnode(udev_device* udev_device) { return UdevLoader::Get()->udev_device_get_devnode(udev_device); } +const char* udev_device_get_devtype(udev_device* udev_device) { + return UdevLoader::Get()->udev_device_get_devtype(udev_device); +} + udev_device* udev_device_get_parent(udev_device* udev_device) { return UdevLoader::Get()->udev_device_get_parent(udev_device); } diff --git a/chromium/device/udev_linux/udev.h b/chromium/device/udev_linux/udev.h index f2670638d61..65ff7b2eef1 100644 --- a/chromium/device/udev_linux/udev.h +++ b/chromium/device/udev_linux/udev.h @@ -31,6 +31,7 @@ namespace device { const char* udev_device_get_action(udev_device* udev_device); const char* udev_device_get_devnode(udev_device* udev_device); +const char* udev_device_get_devtype(udev_device* udev_device); udev_device* udev_device_get_parent(udev_device* udev_device); udev_device* udev_device_get_parent_with_subsystem_devtype( udev_device* udev_device, diff --git a/chromium/device/udev_linux/udev0_loader.cc b/chromium/device/udev_linux/udev0_loader.cc index 027f1c82c48..71b25ad1a51 100644 --- a/chromium/device/udev_linux/udev0_loader.cc +++ b/chromium/device/udev_linux/udev0_loader.cc @@ -27,6 +27,10 @@ const char* Udev0Loader::udev_device_get_devnode(udev_device* udev_device) { return lib_loader_->udev_device_get_devnode(udev_device); } +const char* Udev0Loader::udev_device_get_devtype(udev_device* udev_device) { + return lib_loader_->udev_device_get_devtype(udev_device); +} + udev_device* Udev0Loader::udev_device_get_parent(udev_device* udev_device) { return lib_loader_->udev_device_get_parent(udev_device); } diff --git a/chromium/device/udev_linux/udev0_loader.h b/chromium/device/udev_linux/udev0_loader.h index 5dc39becf97..4126092fd77 100644 --- a/chromium/device/udev_linux/udev0_loader.h +++ b/chromium/device/udev_linux/udev0_loader.h @@ -23,6 +23,7 @@ class Udev0Loader : public UdevLoader { bool Init() override; const char* udev_device_get_action(udev_device* udev_device) override; const char* udev_device_get_devnode(udev_device* udev_device) override; + const char* udev_device_get_devtype(udev_device* udev_device) override; udev_device* udev_device_get_parent(udev_device* udev_device) override; udev_device* udev_device_get_parent_with_subsystem_devtype( udev_device* udev_device, diff --git a/chromium/device/udev_linux/udev1_loader.cc b/chromium/device/udev_linux/udev1_loader.cc index 8ff92ddb089..ef79b96cd8c 100644 --- a/chromium/device/udev_linux/udev1_loader.cc +++ b/chromium/device/udev_linux/udev1_loader.cc @@ -27,6 +27,10 @@ const char* Udev1Loader::udev_device_get_devnode(udev_device* udev_device) { return lib_loader_->udev_device_get_devnode(udev_device); } +const char* Udev1Loader::udev_device_get_devtype(udev_device* udev_device) { + return lib_loader_->udev_device_get_devtype(udev_device); +} + udev_device* Udev1Loader::udev_device_get_parent(udev_device* udev_device) { return lib_loader_->udev_device_get_parent(udev_device); } diff --git a/chromium/device/udev_linux/udev1_loader.h b/chromium/device/udev_linux/udev1_loader.h index 0d8d6616a8c..c082075d608 100644 --- a/chromium/device/udev_linux/udev1_loader.h +++ b/chromium/device/udev_linux/udev1_loader.h @@ -23,6 +23,7 @@ class Udev1Loader : public UdevLoader { bool Init() override; const char* udev_device_get_action(udev_device* udev_device) override; const char* udev_device_get_devnode(udev_device* udev_device) override; + const char* udev_device_get_devtype(udev_device* udev_device) override; udev_device* udev_device_get_parent(udev_device* udev_device) override; udev_device* udev_device_get_parent_with_subsystem_devtype( udev_device* udev_device, diff --git a/chromium/device/udev_linux/udev_loader.h b/chromium/device/udev_linux/udev_loader.h index 4cece61b828..684e3e2da25 100644 --- a/chromium/device/udev_linux/udev_loader.h +++ b/chromium/device/udev_linux/udev_loader.h @@ -45,6 +45,7 @@ class UdevLoader { virtual const char* udev_device_get_action(udev_device* udev_device) = 0; virtual const char* udev_device_get_devnode(udev_device* udev_device) = 0; + virtual const char* udev_device_get_devtype(udev_device* udev_device) = 0; virtual udev_device* udev_device_get_parent(udev_device* udev_device) = 0; virtual udev_device* udev_device_get_parent_with_subsystem_devtype( udev_device* udev_device, diff --git a/chromium/device/vr/android/gvr/gvr_device_provider.cc b/chromium/device/vr/android/gvr/gvr_device_provider.cc index 7e967c15b5d..b5fd9868352 100644 --- a/chromium/device/vr/android/gvr/gvr_device_provider.cc +++ b/chromium/device/vr/android/gvr/gvr_device_provider.cc @@ -17,6 +17,7 @@ GvrDeviceProvider::~GvrDeviceProvider() = default; void GvrDeviceProvider::Initialize( base::RepeatingCallback<void(mojom::XRDeviceId, mojom::VRDisplayInfoPtr, + mojom::XRDeviceDataPtr, mojo::PendingRemote<mojom::XRRuntime>)> add_device_callback, base::RepeatingCallback<void(mojom::XRDeviceId)> remove_device_callback, @@ -32,6 +33,7 @@ void GvrDeviceProvider::Initialize( } if (vr_device_) { add_device_callback.Run(vr_device_->GetId(), vr_device_->GetVRDisplayInfo(), + vr_device_->GetDeviceData(), vr_device_->BindXRRuntime()); } initialized_ = true; diff --git a/chromium/device/vr/android/gvr/gvr_device_provider.h b/chromium/device/vr/android/gvr/gvr_device_provider.h index dcf579f60d8..aed9b3abe96 100644 --- a/chromium/device/vr/android/gvr/gvr_device_provider.h +++ b/chromium/device/vr/android/gvr/gvr_device_provider.h @@ -24,6 +24,7 @@ class DEVICE_VR_EXPORT GvrDeviceProvider : public VRDeviceProvider { void Initialize( base::RepeatingCallback<void(mojom::XRDeviceId, mojom::VRDisplayInfoPtr, + mojom::XRDeviceDataPtr, mojo::PendingRemote<mojom::XRRuntime>)> add_device_callback, base::RepeatingCallback<void(mojom::XRDeviceId)> remove_device_callback, diff --git a/chromium/device/vr/openxr/openxr_device.cc b/chromium/device/vr/openxr/openxr_device.cc index 2b7a8cf0fc9..7e2c2cc0315 100644 --- a/chromium/device/vr/openxr/openxr_device.cc +++ b/chromium/device/vr/openxr/openxr_device.cc @@ -7,8 +7,10 @@ #include <string> #include "base/bind_helpers.h" +#include "build/build_config.h" #include "device/vr/openxr/openxr_api_wrapper.h" #include "device/vr/openxr/openxr_render_loop.h" +#include "device/vr/openxr/openxr_statics.h" #include "device/vr/util/transform_utils.h" #include "mojo/public/cpp/bindings/pending_remote.h" @@ -53,10 +55,17 @@ mojom::VRDisplayInfoPtr CreateFakeVRDisplayInfo(device::mojom::XRDeviceId id) { } // namespace -OpenXrDevice::OpenXrDevice() +// OpenXrDevice must not take ownership of the OpenXrStatics passed in. +// The OpenXrStatics object is owned by IsolatedXRRuntimeProvider. +OpenXrDevice::OpenXrDevice(OpenXrStatics* openxr_statics) : VRDeviceBase(device::mojom::XRDeviceId::OPENXR_DEVICE_ID), weak_ptr_factory_(this) { - SetVRDisplayInfo(CreateFakeVRDisplayInfo(GetId())); + mojom::VRDisplayInfoPtr display_info = CreateFakeVRDisplayInfo(GetId()); + SetVRDisplayInfo(std::move(display_info)); + +#if defined(OS_WIN) + SetLuid(openxr_statics->GetLuid()); +#endif } OpenXrDevice::~OpenXrDevice() { diff --git a/chromium/device/vr/openxr/openxr_device.h b/chromium/device/vr/openxr/openxr_device.h index 5b1d1dc3b1c..237223a3318 100644 --- a/chromium/device/vr/openxr/openxr_device.h +++ b/chromium/device/vr/openxr/openxr_device.h @@ -18,13 +18,14 @@ namespace device { class OpenXrRenderLoop; +class OpenXrStatics; class DEVICE_VR_EXPORT OpenXrDevice : public VRDeviceBase, public mojom::XRSessionController, public mojom::XRCompositorHost { public: - OpenXrDevice(); + OpenXrDevice(OpenXrStatics* openxr_statics); ~OpenXrDevice() override; // VRDeviceBase diff --git a/chromium/device/vr/openxr/openxr_statics.cc b/chromium/device/vr/openxr/openxr_statics.cc index 4b6096f31ad..c10611cb143 100644 --- a/chromium/device/vr/openxr/openxr_statics.cc +++ b/chromium/device/vr/openxr/openxr_statics.cc @@ -31,4 +31,25 @@ bool OpenXrStatics::IsApiAvailable() { XR_SUCCEEDED(CreateInstance(&instance_)); } -} // namespace device
\ No newline at end of file +#if defined(OS_WIN) +// Returns the LUID of the adapter the OpenXR runtime is on. Returns {0, 0} if +// the LUID could not be determined. +LUID OpenXrStatics::GetLuid() { + if (!IsApiAvailable()) + return {0, 0}; + + XrSystemId system; + if (XR_FAILED(GetSystem(instance_, &system))) + return {0, 0}; + + XrGraphicsRequirementsD3D11KHR graphics_requirements = { + XR_TYPE_GRAPHICS_REQUIREMENTS_D3D11_KHR}; + if (XR_FAILED(xrGetD3D11GraphicsRequirementsKHR(instance_, system, + &graphics_requirements))) + return {0, 0}; + + return graphics_requirements.adapterLuid; +} +#endif + +} // namespace device diff --git a/chromium/device/vr/openxr/openxr_statics.h b/chromium/device/vr/openxr/openxr_statics.h index 0b41f576574..4663b8a41fa 100644 --- a/chromium/device/vr/openxr/openxr_statics.h +++ b/chromium/device/vr/openxr/openxr_statics.h @@ -8,6 +8,7 @@ #include <d3d11.h> #include <memory> +#include "build/build_config.h" #include "device/vr/vr_export.h" #include "third_party/openxr/src/include/openxr/openxr.h" #include "third_party/openxr/src/include/openxr/openxr_platform.h" @@ -22,10 +23,14 @@ class DEVICE_VR_EXPORT OpenXrStatics { bool IsHardwareAvailable(); bool IsApiAvailable(); +#if defined(OS_WIN) + LUID GetLuid(); +#endif + private: XrInstance instance_; }; } // namespace device -#endif // DEVICE_VR_WINDOWS_MIXED_REALITY_MIXED_REALITY_STATICS_H_
\ No newline at end of file +#endif // DEVICE_VR_WINDOWS_MIXED_REALITY_MIXED_REALITY_STATICS_H_ diff --git a/chromium/device/vr/openxr/openxr_util.h b/chromium/device/vr/openxr/openxr_util.h index ed5ed7c6e9c..80aaeae67cb 100644 --- a/chromium/device/vr/openxr/openxr_util.h +++ b/chromium/device/vr/openxr/openxr_util.h @@ -5,6 +5,7 @@ #ifndef DEVICE_VR_OPENXR_OPENXR_UTIL_H_ #define DEVICE_VR_OPENXR_OPENXR_UTIL_H_ +#include "base/logging.h" #include "third_party/openxr/src/include/openxr/openxr.h" namespace device { diff --git a/chromium/device/vr/orientation/orientation_device_provider.cc b/chromium/device/vr/orientation/orientation_device_provider.cc index 7745bc513fb..785f43288a8 100644 --- a/chromium/device/vr/orientation/orientation_device_provider.cc +++ b/chromium/device/vr/orientation/orientation_device_provider.cc @@ -22,13 +22,14 @@ VROrientationDeviceProvider::~VROrientationDeviceProvider() = default; void VROrientationDeviceProvider::Initialize( base::RepeatingCallback<void(mojom::XRDeviceId, mojom::VRDisplayInfoPtr, + mojom::XRDeviceDataPtr, mojo::PendingRemote<mojom::XRRuntime>)> add_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(), - device_->BindXRRuntime()); + device_->GetDeviceData(), device_->BindXRRuntime()); return; } @@ -55,6 +56,7 @@ void VROrientationDeviceProvider::DeviceInitialized() { // If the device successfully connected to the orientation APIs, provide it. if (device_->IsAvailable()) { add_device_callback_.Run(device_->GetId(), device_->GetVRDisplayInfo(), + device_->GetDeviceData(), device_->BindXRRuntime()); } diff --git a/chromium/device/vr/orientation/orientation_device_provider.h b/chromium/device/vr/orientation/orientation_device_provider.h index 1d6e8149a88..6d988f1cd52 100644 --- a/chromium/device/vr/orientation/orientation_device_provider.h +++ b/chromium/device/vr/orientation/orientation_device_provider.h @@ -28,6 +28,7 @@ class COMPONENT_EXPORT(VR_ORIENTATION) VROrientationDeviceProvider void Initialize( base::RepeatingCallback<void(mojom::XRDeviceId, mojom::VRDisplayInfoPtr, + mojom::XRDeviceDataPtr, mojo::PendingRemote<mojom::XRRuntime>)> add_device_callback, base::RepeatingCallback<void(mojom::XRDeviceId)> remove_device_callback, @@ -46,6 +47,7 @@ class COMPONENT_EXPORT(VR_ORIENTATION) VROrientationDeviceProvider base::RepeatingCallback<void(mojom::XRDeviceId, mojom::VRDisplayInfoPtr, + mojom::XRDeviceDataPtr, mojo::PendingRemote<mojom::XRRuntime>)> 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 7f257ce8390..f40c5f35024 100644 --- a/chromium/device/vr/orientation/orientation_device_provider_unittest.cc +++ b/chromium/device/vr/orientation/orientation_device_provider_unittest.cc @@ -84,10 +84,12 @@ class VROrientationDeviceProviderTest : public testing::Test { base::RepeatingCallback<void(device::mojom::XRDeviceId, mojom::VRDisplayInfoPtr, + mojom::XRDeviceDataPtr, mojo::PendingRemote<mojom::XRRuntime> device)> DeviceAndIdCallbackFailIfCalled() { return base::BindRepeating( [](device::mojom::XRDeviceId id, mojom::VRDisplayInfoPtr, + mojom::XRDeviceDataPtr, mojo::PendingRemote<mojom::XRRuntime> device) { FAIL(); }); } @@ -98,14 +100,16 @@ class VROrientationDeviceProviderTest : public testing::Test { base::RepeatingCallback<void(device::mojom::XRDeviceId, mojom::VRDisplayInfoPtr, + mojom::XRDeviceDataPtr, mojo::PendingRemote<mojom::XRRuntime> device)> DeviceAndIdCallbackMustBeCalled(base::RunLoop* loop) { return base::BindRepeating( [](base::OnceClosure quit_closure, device::mojom::XRDeviceId id, - mojom::VRDisplayInfoPtr info, + mojom::VRDisplayInfoPtr info, mojom::XRDeviceDataPtr data, mojo::PendingRemote<mojom::XRRuntime> device) { ASSERT_TRUE(device); ASSERT_TRUE(info); + ASSERT_TRUE(data); std::move(quit_closure).Run(); }, loop->QuitClosure()); diff --git a/chromium/device/vr/public/cpp/vr_device_provider.h b/chromium/device/vr/public/cpp/vr_device_provider.h index 10c3460fb5a..f3ecf6a2d6a 100644 --- a/chromium/device/vr/public/cpp/vr_device_provider.h +++ b/chromium/device/vr/public/cpp/vr_device_provider.h @@ -23,6 +23,7 @@ class COMPONENT_EXPORT(VR_PUBLIC_CPP) VRDeviceProvider { virtual void Initialize( base::RepeatingCallback<void(mojom::XRDeviceId id, mojom::VRDisplayInfoPtr, + mojom::XRDeviceDataPtr, mojo::PendingRemote<mojom::XRRuntime>)> add_device_callback, base::RepeatingCallback<void(mojom::XRDeviceId id)> diff --git a/chromium/device/vr/public/mojom/BUILD.gn b/chromium/device/vr/public/mojom/BUILD.gn index 20a26a69965..56752b7b348 100644 --- a/chromium/device/vr/public/mojom/BUILD.gn +++ b/chromium/device/vr/public/mojom/BUILD.gn @@ -48,8 +48,13 @@ mojom_component("mojom") { mojom = "device.mojom.RgbTupleF32" cpp = "::device::RgbTupleF32" }, + { + mojom = "device.mojom.Pose" + cpp = "::device::Pose" + }, ] traits_headers = [ "//device/vr/public/mojom/vr_service_mojom_traits.h" ] + traits_public_deps = [ ":vr_public_typemaps" ] } cpp_typemaps = [ shared_cpp_typemap ] @@ -67,3 +72,21 @@ mojom_component("test_mojom") { "//ui/gfx/mojom", ] } + +component("vr_public_typemaps") { + output_name = "device_vr_public_typemaps" + + defines = [ "IS_VR_PUBLIC_TYPEMAPS_IMPL" ] + + sources = [ + "pose.cc", + "pose.h", + ] + + deps = [ + "//base:base", + "//skia", + "//ui/gfx:geometry_skia", + "//ui/gfx/geometry:geometry", + ] +} diff --git a/chromium/device/vr/public/mojom/isolated_xr_service.mojom b/chromium/device/vr/public/mojom/isolated_xr_service.mojom index 787636c99a9..12235c2a141 100644 --- a/chromium/device/vr/public/mojom/isolated_xr_service.mojom +++ b/chromium/device/vr/public/mojom/isolated_xr_service.mojom @@ -6,6 +6,8 @@ module device.mojom; import "device/vr/public/mojom/browser_test_interfaces.mojom"; import "device/vr/public/mojom/vr_service.mojom"; +[EnableIf=is_win] +import "gpu/ipc/common/luid.mojom"; import "mojo/public/mojom/base/time.mojom"; import "ui/gfx/geometry/mojom/geometry.mojom"; @@ -110,6 +112,15 @@ interface XRCompositorHost { CreateImmersiveOverlay(pending_receiver<ImmersiveOverlay> overlay); }; +// Information about a particular XR device that is available. All information +// about an available XR device should be wrapped in this struct. +// TODO(crbug.com/1090029): Wrap XRDeviceId + VRDisplayInfo in this struct +struct XRDeviceData { + [EnableIf=is_win] + // The LUID of the GPU that the device is plugged into. + gpu.mojom.Luid? luid; +}; + // 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. @@ -118,6 +129,7 @@ interface IsolatedXRRuntimeProviderClient { // attached and become available. OnDeviceAdded(pending_remote<XRRuntime> runtime, pending_remote<XRCompositorHost> compositor_host, + XRDeviceData device_data, device.mojom.XRDeviceId device_id); // Called when runtimes become unavailable - for example if the hardware is diff --git a/chromium/device/vr/public/mojom/pose.cc b/chromium/device/vr/public/mojom/pose.cc new file mode 100644 index 00000000000..4fbf54010d3 --- /dev/null +++ b/chromium/device/vr/public/mojom/pose.cc @@ -0,0 +1,40 @@ +// Copyright 2020 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/public/mojom/pose.h" + +#include "ui/gfx/transform_util.h" + +namespace device { + +Pose::Pose() = default; + +Pose::Pose(const gfx::Point3F& position, const gfx::Quaternion& orientation) + : position_(position), orientation_(orientation) { + gfx::DecomposedTransform decomposed_pose; + decomposed_pose.translate[0] = position.x(); + decomposed_pose.translate[1] = position.y(); + decomposed_pose.translate[2] = position.z(); + decomposed_pose.quaternion = orientation; + + other_from_this_ = gfx::ComposeTransform(decomposed_pose); +} + +base::Optional<Pose> Pose::Create(const gfx::Transform& other_from_this) { + gfx::DecomposedTransform decomposed_other_from_this; + if (!gfx::DecomposeTransform(&decomposed_other_from_this, other_from_this)) { + return base::nullopt; + } + + return Pose(gfx::Point3F(decomposed_other_from_this.translate[0], + decomposed_other_from_this.translate[1], + decomposed_other_from_this.translate[2]), + decomposed_other_from_this.quaternion); +} + +const gfx::Transform& Pose::ToTransform() const { + return other_from_this_; +} + +} // namespace device diff --git a/chromium/device/vr/public/mojom/pose.h b/chromium/device/vr/public/mojom/pose.h new file mode 100644 index 00000000000..524e7294f63 --- /dev/null +++ b/chromium/device/vr/public/mojom/pose.h @@ -0,0 +1,65 @@ +// Copyright 2020 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_PUBLIC_MOJOM_POSE_H_ +#define DEVICE_VR_PUBLIC_MOJOM_POSE_H_ + +#include "base/component_export.h" +#include "base/optional.h" +#include "ui/gfx/geometry/point3_f.h" +#include "ui/gfx/geometry/quaternion.h" +#include "ui/gfx/transform.h" + +namespace device { + +// Pose represents some entity's position and orientation and is always +// expressed relative to some coordinate system. Alternatively, the pose can be +// viewed as a rigid transform that performs a coordinate system change from the +// coordinate system represented by the entity (i.e. coordinate system whose +// origin is equal to entity's position and whose orientation is equal to +// entity's orientation) to the coordinate system relative to which the pose is +// supposed to be expressed. +// +// If the pose represents a position of entity |entity| in coordinate space +// |coord|, it is recommended to name such a variable as |coord_from_entity|. +// In order to obtain the matrix that performs the coordinate system +// transformation, the callers can use |GetOtherFromThis()| method. The +// resulting matrix will encode coordinate system change from |entity| space to +// |coord| space. +// +// The source for the naming convention can be found here: +// https://www.sebastiansylvan.com/post/matrix_naming_convention/ +class COMPONENT_EXPORT(VR_PUBLIC_TYPEMAPS) Pose { + public: + explicit Pose(); + explicit Pose(const gfx::Point3F& position, + const gfx::Quaternion& orientation); + + // Creates a pose from transform by decomposing it. The method assumes that + // the passed in matrix represents a rigid transformation (i.e. only the + // orientation and translation components of the decomposed matrix will affect + // the result). If the matrix could not be decomposed, the method will return + // a base::nullopt. + static base::Optional<Pose> Create(const gfx::Transform& other_from_this); + + const gfx::Point3F& position() const { return position_; } + + const gfx::Quaternion& orientation() const { return orientation_; } + + // Returns the underlying matrix representation of the pose. + const gfx::Transform& ToTransform() const; + + private: + gfx::Point3F position_; + gfx::Quaternion orientation_; + + // Transformation that can be used to switch from coordinate system + // represented by this pose to other coordinate system (i.e. the coordinate + // system relative to which this pose is supposed to be expressed). + gfx::Transform other_from_this_; +}; + +} // namespace device + +#endif // DEVICE_VR_PUBLIC_MOJOM_POSE_H_ diff --git a/chromium/device/vr/public/mojom/vr_service.mojom b/chromium/device/vr/public/mojom/vr_service.mojom index 57de335d954..ee80526b2f5 100644 --- a/chromium/device/vr/public/mojom/vr_service.mojom +++ b/chromium/device/vr/public/mojom/vr_service.mojom @@ -60,6 +60,7 @@ enum XRSessionFeature { HIT_TEST = 7, LIGHT_ESTIMATION = 8, // Experimental feature. ANCHORS = 9, // Experimental feature. + CAMERA_ACCESS = 10, // Experimental feature. }; // These values are persisted to logs. Entries should not be renumbered and @@ -261,7 +262,7 @@ struct XRRay { }; struct XRHitResult { - gfx.mojom.Transform hit_matrix; + Pose mojo_from_result; // Pose of the result (aka intersection point). uint64 plane_id; // If the hit test result was computed based off of a plane, // the plane ID will be stored here. 0 signifies no plane. // TODO(https://crbug.com/657632) - make it optional once @@ -329,14 +330,14 @@ struct XRPresentationTransportOptions { bool wait_for_gpu_fence; }; -// Native origins that are reference spaces are identified by their category. -// Currently, the device needs to know about 3 reference space categories. -enum XRReferenceSpaceCategory { - LOCAL, - LOCAL_FLOOR, - VIEWER, - BOUNDED_FLOOR, - UNBOUNDED +// Native origins that are reference spaces are identified by their type. +// Used for metrics, don't remove or change values. +enum XRReferenceSpaceType { + kViewer = 0, + kLocal = 1, + kLocalFloor = 2, + kBoundedFloor = 3, + kUnbounded = 4, }; // Native origin represents a reference space that is known to the device and @@ -346,12 +347,12 @@ enum XRReferenceSpaceCategory { // XRReferenceSpaceType, XRBoundedReferenceSpace) or returns an XRSpace (for // example XRAnchor, XRPlane, XRInputSource). Native origin can be identified, // depending on its type, by the id of the entity (this is the case for planes, -// anchors and input sources), or by reference space category. +// anchors and input sources), or by reference space type. union XRNativeOriginInformation { uint32 input_source_id; uint64 plane_id; uint64 anchor_id; - XRReferenceSpaceCategory reference_space_category; + XRReferenceSpaceType reference_space_type; }; enum XRPlaneOrientation { @@ -540,8 +541,7 @@ struct XRFrameData { // The pose may be null if the device lost tracking. The XRFrameData can still // have other data, such as pass through camera image. VRPose? pose; - // TODO(https://crbug.com/838515): Is this delta since the last - // frame? OR an unspecified origin? Something else? + // Time delta from an unspecified origin. mojo_base.mojom.TimeDelta time_delta; // The buffer_holder is used for sending data imagery back and forth across // the process boundary. For application with pass through camera, it holds @@ -655,6 +655,19 @@ union RequestSessionResult { RequestSessionError failure_reason; }; +// Return value for VRService.MakeXrCompatible() to indicate whether the GPU +// process is compatible with the active VR headset. +enum XrCompatibleResult { + // Compatibility results where the GPU process was not restarted. + kAlreadyCompatible, + kNotCompatible, + + // Compatibility results where the GPU was restarted. Context lost and + // restored for existing WebGL contexts must be handled before using for XR. + kCompatibleAfterRestart, // XR compatible, GPU process was restarted. + kNotCompatibleAfterRestart, // Not XR compatible, GPU process was restarted. +}; + // Interface for requesting XRDevice interfaces and registering for // notifications that the XRDevice has changed. Implemented in the browser // process and consumed in the renderer process. @@ -681,6 +694,17 @@ interface VRService { // renderer can (and may) still decide to submit frames and that should not be // treated as illegal or clear the throttled state. SetFramesThrottled(bool throttled); + + // Request for the GPU process to be compatible with the active VR headset. + // This will trigger the initialization of the XR process if it isn't already + // initialized and then restart the GPU process if it's not using the same GPU + // as the VR headset. The Sync attribute is needed because there are two ways + // for WebXR to trigger this - WebGLRenderingContext.makeXRCompatible() + // which returns a promise and is asynchronous, and + // HTMLCanvasElement.getContext('webgl', { xrCompatible: true }) which is + // synchronous and must return a context that is already xr compatible. + [Sync] + MakeXrCompatible() => (XrCompatibleResult xr_compatible_result); }; // Any metrics that must be recorded throughout the duration of an XR session @@ -770,7 +794,11 @@ interface XREnvironmentIntegrationProvider { // ignored. UnsubscribeFromHitTest(uint64 subscription_id); - // Issues a request to create an anchor attached to a session. + // Issues a request to create a free-floating anchor (not attached to any + // particular real world entity). + // |native_origin_information| specifies native origin relative to which the + // anchor is supposed to be created. |native_origin_from_anchor| describes the + // desired pose of the newly created anchor. // |result| will contain status code of the request. |anchor_id| will be valid // only if the |result| is SUCCESS. CreateAnchor( @@ -782,11 +810,18 @@ interface XREnvironmentIntegrationProvider { // not interested in obtaining detailed error information from the device. // Issues a request to create an anchor attached to a plane. + // |native_origin_information| specifies native origin relative to which the + // anchor is supposed to be created. |native_origin_from_anchor| describes the + // desired pose of the newly created anchor. |plane_id| identifies which plane + // the anchor will be attached to. // |result| will contain status code of the request. |anchor_id| will be valid // only if the |result| is SUCCESS. - CreatePlaneAnchor(Pose plane_from_anchor, uint64 plane_id) => - (CreateAnchorResult result, uint64 anchor_id); - // TODO(https://crbug.com/657632): Ditto - make anchor_id a nullable integer.. + CreatePlaneAnchor( + XRNativeOriginInformation native_origin_information, + Pose native_origin_from_anchor, + uint64 plane_id) + => (CreateAnchorResult result, uint64 anchor_id); + // TODO(https://crbug.com/657632): Ditto - make anchor_id a nullable integer. // Detaches an existing anchor. The |anchor_id| must be a valid id of an // anchor created by one of the CreateAnchor calls, otherwise the call will be diff --git a/chromium/device/vr/public/mojom/vr_service_mojom_traits.h b/chromium/device/vr/public/mojom/vr_service_mojom_traits.h index c1967918fa2..fe0f57c63d8 100644 --- a/chromium/device/vr/public/mojom/vr_service_mojom_traits.h +++ b/chromium/device/vr/public/mojom/vr_service_mojom_traits.h @@ -5,10 +5,14 @@ #ifndef DEVICE_VR_PUBLIC_MOJOM_VR_SERVICE_MOJOM_TRAITS_H_ #define DEVICE_VR_PUBLIC_MOJOM_VR_SERVICE_MOJOM_TRAITS_H_ +#include "device/vr/public/mojom/pose.h" #include "device/vr/public/mojom/rgb_tuple_f32.h" #include "device/vr/public/mojom/rgba_tuple_f16.h" #include "device/vr/public/mojom/vr_service.mojom-shared.h" #include "mojo/public/cpp/bindings/struct_traits.h" +#include "ui/gfx/geometry/mojom/geometry_mojom_traits.h" +#include "ui/gfx/geometry/point3_f.h" +#include "ui/gfx/geometry/quaternion.h" namespace mojo { @@ -46,6 +50,33 @@ struct StructTraits<device::mojom::RgbTupleF32DataView, device::RgbTupleF32> { } }; +template <> +class StructTraits<device::mojom::PoseDataView, device::Pose> { + public: + static const gfx::Point3F& position(const device::Pose& pose) { + return pose.position(); + } + static const gfx::Quaternion& orientation(const device::Pose& pose) { + return pose.orientation(); + } + + static bool Read(device::mojom::PoseDataView pose_data, + device::Pose* out_pose) { + gfx::Point3F position; + if (!pose_data.ReadPosition(&position)) { + return false; + } + + gfx::Quaternion orientation; + if (!pose_data.ReadOrientation(&orientation)) { + return false; + } + + *out_pose = device::Pose(position, orientation); + return true; + } +}; + } // namespace mojo #endif // DEVICE_VR_PUBLIC_MOJOM_VR_SERVICE_MOJOM_TRAITS_H_ diff --git a/chromium/device/vr/test/test_hook.h b/chromium/device/vr/test/test_hook.h index c7863c1ac4c..6c2db8e236b 100644 --- a/chromium/device/vr/test/test_hook.h +++ b/chromium/device/vr/test/test_hook.h @@ -5,7 +5,7 @@ #ifndef DEVICE_VR_TEST_TEST_HOOK_H_ #define DEVICE_VR_TEST_TEST_HOOK_H_ -#include "base/logging.h" +#include "base/check.h" #include "device/vr/public/mojom/browser_test_interfaces.mojom.h" #include "ui/gfx/transform.h" diff --git a/chromium/device/vr/vr_device_base.cc b/chromium/device/vr/vr_device_base.cc index 61a6404614c..9d113bb653d 100644 --- a/chromium/device/vr/vr_device_base.cc +++ b/chromium/device/vr/vr_device_base.cc @@ -19,6 +19,10 @@ mojom::XRDeviceId VRDeviceBase::GetId() const { return id_; } +mojom::XRDeviceDataPtr VRDeviceBase::GetDeviceData() const { + return device_data_.Clone(); +} + void VRDeviceBase::PauseTracking() {} void VRDeviceBase::ResumeTracking() {} @@ -75,6 +79,15 @@ void VRDeviceBase::OnVisibilityStateChanged( listener_->OnVisibilityStateChanged(visibility_state); } +#if defined(OS_WIN) +void VRDeviceBase::SetLuid(const LUID& luid) { + if (luid.HighPart != 0 || luid.LowPart != 0) { + // Only set the LUID if it exists and is nonzero. + device_data_.luid = base::make_optional<LUID>(luid); + } +} +#endif + mojo::PendingRemote<mojom::XRRuntime> VRDeviceBase::BindXRRuntime() { DVLOG(2) << __func__; return runtime_receiver_.BindNewPipeAndPassRemote(); diff --git a/chromium/device/vr/vr_device_base.h b/chromium/device/vr/vr_device_base.h index 7a33f90385e..c16a63ca192 100644 --- a/chromium/device/vr/vr_device_base.h +++ b/chromium/device/vr/vr_device_base.h @@ -11,6 +11,7 @@ #include "base/callback.h" #include "base/component_export.h" #include "base/macros.h" +#include "build/build_config.h" #include "device/vr/public/mojom/vr_service.mojom.h" #include "device/vr/vr_device.h" #include "mojo/public/cpp/bindings/associated_remote.h" @@ -35,6 +36,7 @@ class COMPONENT_EXPORT(DEVICE_VR_BASE) VRDeviceBase : public mojom::XRRuntime { void ShutdownSession(mojom::XRRuntime::ShutdownSessionCallback) override; device::mojom::XRDeviceId GetId() const; + device::mojom::XRDeviceDataPtr GetDeviceData() const; bool HasExclusiveSession(); @@ -61,6 +63,9 @@ class COMPONENT_EXPORT(DEVICE_VR_BASE) VRDeviceBase : public mojom::XRRuntime { bool IsPresenting() { return presenting_; } // Exposed for test. void SetVRDisplayInfo(mojom::VRDisplayInfoPtr display_info); void OnVisibilityStateChanged(mojom::XRVisibilityState visibility_state); +#if defined(OS_WIN) + void SetLuid(const LUID& luid); +#endif mojom::VRDisplayInfoPtr display_info_; @@ -73,6 +78,8 @@ class COMPONENT_EXPORT(DEVICE_VR_BASE) VRDeviceBase : public mojom::XRRuntime { device::mojom::XRDeviceId id_; + device::mojom::XRDeviceData device_data_; + mojo::Receiver<mojom::XRRuntime> runtime_receiver_{this}; DISALLOW_COPY_AND_ASSIGN(VRDeviceBase); |