summaryrefslogtreecommitdiff
path: root/chromium/device
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2020-11-18 16:35:47 +0100
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2020-11-18 15:45:54 +0000
commit32f5a1c56531e4210bc4cf8d8c7825d66e081888 (patch)
treeeeeec6822f4d738d8454525233fd0e2e3a659e6d /chromium/device
parent99677208ff3b216fdfec551fbe548da5520cd6fb (diff)
downloadqtwebengine-chromium-32f5a1c56531e4210bc4cf8d8c7825d66e081888.tar.gz
BASELINE: Update Chromium to 87.0.4280.67
Change-Id: Ib157360be8c2ffb2c73125751a89f60e049c1d54 Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/device')
-rw-r--r--chromium/device/BUILD.gn11
-rw-r--r--chromium/device/base/BUILD.gn7
-rw-r--r--chromium/device/base/features.cc21
-rw-r--r--chromium/device/base/features.h6
-rw-r--r--chromium/device/base/synchronization/one_writer_seqlock.cc43
-rw-r--r--chromium/device/base/synchronization/one_writer_seqlock.h14
-rw-r--r--chromium/device/base/synchronization/one_writer_seqlock_unittest.cc97
-rw-r--r--chromium/device/bluetooth/BUILD.gn11
-rw-r--r--chromium/device/bluetooth/adapter.cc89
-rw-r--r--chromium/device/bluetooth/adapter.h21
-rw-r--r--chromium/device/bluetooth/adapter_unittest.cc126
-rw-r--r--chromium/device/bluetooth/advertisement.cc46
-rw-r--r--chromium/device/bluetooth/advertisement.h43
-rw-r--r--chromium/device/bluetooth/advertisement_unittest.cc73
-rw-r--r--chromium/device/bluetooth/android/java/DEPS3
-rw-r--r--chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothAdapter.java370
-rw-r--r--chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothDevice.java326
-rw-r--r--chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothRemoteGattCharacteristic.java196
-rw-r--r--chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothRemoteGattDescriptor.java123
-rw-r--r--chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothRemoteGattService.java91
-rw-r--r--chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothScanFilterBuilder.java58
-rw-r--r--chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothScanFilterList.java47
-rw-r--r--chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/Wrappers.java643
-rw-r--r--chromium/device/bluetooth/bluetooth_adapter.h7
-rw-r--r--chromium/device/bluetooth/bluetooth_adapter_factory_wrapper.cc150
-rw-r--r--chromium/device/bluetooth/bluetooth_adapter_factory_wrapper.h97
-rw-r--r--chromium/device/bluetooth/bluetooth_adapter_winrt.cc9
-rw-r--r--chromium/device/bluetooth/bluetooth_classic_device_mac.h1
-rw-r--r--chromium/device/bluetooth/bluetooth_classic_device_mac.mm5
-rw-r--r--chromium/device/bluetooth/bluetooth_device.h11
-rw-r--r--chromium/device/bluetooth/bluetooth_device_android.cc5
-rw-r--r--chromium/device/bluetooth/bluetooth_device_android.h1
-rw-r--r--chromium/device/bluetooth/bluetooth_device_win.cc5
-rw-r--r--chromium/device/bluetooth/bluetooth_device_win.h1
-rw-r--r--chromium/device/bluetooth/bluetooth_device_winrt.cc5
-rw-r--r--chromium/device/bluetooth/bluetooth_device_winrt.h1
-rw-r--r--chromium/device/bluetooth/bluetooth_low_energy_device_mac.h1
-rw-r--r--chromium/device/bluetooth/bluetooth_low_energy_device_mac.mm6
-rw-r--r--chromium/device/bluetooth/bluetooth_socket_mac.mm21
-rw-r--r--chromium/device/bluetooth/bluetooth_socket_net.cc43
-rw-r--r--chromium/device/bluetooth/bluetooth_socket_net.h1
-rw-r--r--chromium/device/bluetooth/bluez/bluetooth_bluez_unittest.cc27
-rw-r--r--chromium/device/bluetooth/bluez/bluetooth_device_bluez.cc18
-rw-r--r--chromium/device/bluetooth/bluez/bluetooth_device_bluez.h1
-rw-r--r--chromium/device/bluetooth/cast/bluetooth_device_cast.cc13
-rw-r--r--chromium/device/bluetooth/cast/bluetooth_device_cast.h3
-rw-r--r--chromium/device/bluetooth/dbus/bluetooth_device_client.cc2
-rw-r--r--chromium/device/bluetooth/dbus/bluetooth_device_client.h3
-rw-r--r--chromium/device/bluetooth/dbus/bluetooth_le_advertisement_service_provider.cc1
-rw-r--r--chromium/device/bluetooth/device.cc3
-rw-r--r--chromium/device/bluetooth/public/mojom/adapter.mojom39
-rw-r--r--chromium/device/bluetooth/public/mojom/device.mojom21
-rw-r--r--chromium/device/fido/BUILD.gn53
-rw-r--r--chromium/device/fido/DEPS2
-rw-r--r--chromium/device/fido/aoa/android_accessory_discovery.cc1
-rw-r--r--chromium/device/fido/authenticator_get_assertion_response.cc5
-rw-r--r--chromium/device/fido/authenticator_get_assertion_response.h12
-rw-r--r--chromium/device/fido/authenticator_get_info_response.cc2
-rw-r--r--chromium/device/fido/authenticator_make_credential_response.cc17
-rw-r--r--chromium/device/fido/authenticator_make_credential_response.h17
-rw-r--r--chromium/device/fido/authenticator_selection_criteria.cc6
-rw-r--r--chromium/device/fido/authenticator_selection_criteria.h12
-rw-r--r--chromium/device/fido/authenticator_supported_options.cc4
-rw-r--r--chromium/device/fido/authenticator_supported_options.h3
-rw-r--r--chromium/device/fido/bio/enrollment.h2
-rw-r--r--chromium/device/fido/cable/cable_discovery_data.cc203
-rw-r--r--chromium/device/fido/cable/cable_discovery_data.h127
-rw-r--r--chromium/device/fido/cable/fido_ble_connection_unittest.cc3
-rw-r--r--chromium/device/fido/cable/fido_cable_discovery.cc182
-rw-r--r--chromium/device/fido/cable/fido_cable_discovery.h74
-rw-r--r--chromium/device/fido/cable/fido_cable_discovery_unittest.cc10
-rw-r--r--chromium/device/fido/cable/fido_tunnel_device.cc266
-rw-r--r--chromium/device/fido/cable/fido_tunnel_device.h65
-rw-r--r--chromium/device/fido/cable/noise.cc4
-rw-r--r--chromium/device/fido/cable/noise.h1
-rw-r--r--chromium/device/fido/cable/v2_authenticator.cc887
-rw-r--r--chromium/device/fido/cable/v2_authenticator.h128
-rw-r--r--chromium/device/fido/cable/v2_constants.h44
-rw-r--r--chromium/device/fido/cable/v2_discovery.cc119
-rw-r--r--chromium/device/fido/cable/v2_discovery.h74
-rw-r--r--chromium/device/fido/cable/v2_handshake.cc231
-rw-r--r--chromium/device/fido/cable/v2_handshake.h157
-rw-r--r--chromium/device/fido/cable/v2_handshake_fuzzer.cc13
-rw-r--r--chromium/device/fido/cable/v2_handshake_unittest.cc126
-rw-r--r--chromium/device/fido/cable/v2_registration.cc177
-rw-r--r--chromium/device/fido/cable/v2_registration.h64
-rw-r--r--chromium/device/fido/cable/v2_test_util.cc483
-rw-r--r--chromium/device/fido/cable/v2_test_util.h52
-rw-r--r--chromium/device/fido/cable/websocket_adapter.cc117
-rw-r--r--chromium/device/fido/cable/websocket_adapter.h20
-rw-r--r--chromium/device/fido/client_data.cc2
-rw-r--r--chromium/device/fido/credential_management.cc96
-rw-r--r--chromium/device/fido/credential_management.h27
-rw-r--r--chromium/device/fido/credential_management_handler.cc2
-rw-r--r--chromium/device/fido/credential_management_handler.h2
-rw-r--r--chromium/device/fido/ctap_get_assertion_request.h6
-rw-r--r--chromium/device/fido/ctap_make_credential_request.cc9
-rw-r--r--chromium/device/fido/ctap_make_credential_request.h5
-rw-r--r--chromium/device/fido/device_response_converter.cc48
-rw-r--r--chromium/device/fido/fake_fido_discovery.cc16
-rw-r--r--chromium/device/fido/fake_fido_discovery.h2
-rw-r--r--chromium/device/fido/fake_fido_discovery_unittest.cc9
-rw-r--r--chromium/device/fido/fido_authenticator.cc21
-rw-r--r--chromium/device/fido/fido_authenticator.h28
-rw-r--r--chromium/device/fido/fido_constants.cc2
-rw-r--r--chromium/device/fido/fido_constants.h136
-rw-r--r--chromium/device/fido/fido_device_authenticator.cc216
-rw-r--r--chromium/device/fido/fido_device_authenticator.h55
-rw-r--r--chromium/device/fido/fido_device_authenticator_unittest.cc218
-rw-r--r--chromium/device/fido/fido_device_discovery.cc1
-rw-r--r--chromium/device/fido/fido_device_discovery.h9
-rw-r--r--chromium/device/fido/fido_discovery_factory.cc72
-rw-r--r--chromium/device/fido/fido_discovery_factory.h23
-rw-r--r--chromium/device/fido/fido_parsing_utils.h11
-rw-r--r--chromium/device/fido/fido_request_handler_base.cc10
-rw-r--r--chromium/device/fido/fido_types.h23
-rw-r--r--chromium/device/fido/get_assertion_request_handler.cc59
-rw-r--r--chromium/device/fido/get_assertion_request_handler.h3
-rw-r--r--chromium/device/fido/hid/fake_hid_impl_for_testing.h3
-rw-r--r--chromium/device/fido/large_blob.cc299
-rw-r--r--chromium/device/fido/large_blob.h203
-rw-r--r--chromium/device/fido/large_blob_unittest.cc144
-rw-r--r--chromium/device/fido/mac/browsing_data_deletion_unittest.mm7
-rw-r--r--chromium/device/fido/mac/credential_store.mm18
-rw-r--r--chromium/device/fido/mac/make_credential_operation.mm1
-rw-r--r--chromium/device/fido/mac/touch_id_context.mm4
-rw-r--r--chromium/device/fido/make_credential_handler_unittest.cc176
-rw-r--r--chromium/device/fido/make_credential_request_handler.cc186
-rw-r--r--chromium/device/fido/make_credential_request_handler.h36
-rw-r--r--chromium/device/fido/make_credential_task.cc59
-rw-r--r--chromium/device/fido/pin.h11
-rw-r--r--chromium/device/fido/virtual_ctap2_device.cc259
-rw-r--r--chromium/device/fido/virtual_ctap2_device.h15
-rw-r--r--chromium/device/fido/virtual_fido_device.cc7
-rw-r--r--chromium/device/fido/virtual_fido_device.h13
-rw-r--r--chromium/device/fido/virtual_fido_device_factory.cc10
-rw-r--r--chromium/device/fido/virtual_fido_device_factory.h2
-rw-r--r--chromium/device/fido/win/logging.cc4
-rw-r--r--chromium/device/fido/win/webauthn_api.cc13
-rw-r--r--chromium/device/gamepad/BUILD.gn7
-rw-r--r--chromium/device/gamepad/android/java/src/org/chromium/device/gamepad/GamepadDevice.java212
-rw-r--r--chromium/device/gamepad/android/java/src/org/chromium/device/gamepad/GamepadList.java332
-rw-r--r--chromium/device/gamepad/android/java/src/org/chromium/device/gamepad/GamepadMappings.java642
-rw-r--r--chromium/device/gamepad/gamepad_provider.cc4
-rw-r--r--chromium/device/gamepad/public/cpp/gamepad_features.cc47
-rw-r--r--chromium/device/gamepad/public/cpp/gamepad_features.h3
-rw-r--r--chromium/device/gamepad/udev_gamepad_linux.cc11
-rw-r--r--chromium/device/vr/BUILD.gn82
-rw-r--r--chromium/device/vr/DEPS3
-rw-r--r--chromium/device/vr/README.md5
-rw-r--r--chromium/device/vr/android/BUILD.gn17
-rw-r--r--chromium/device/vr/android/DEPS5
-rw-r--r--chromium/device/vr/android/arcore/BUILD.gn17
-rw-r--r--chromium/device/vr/android/arcore/DEPS4
-rw-r--r--chromium/device/vr/android/arcore/ar_image_transport.cc408
-rw-r--r--chromium/device/vr/android/arcore/ar_image_transport.h147
-rw-r--r--chromium/device/vr/android/arcore/ar_renderer.cc125
-rw-r--r--chromium/device/vr/android/arcore/ar_renderer.h36
-rw-r--r--chromium/device/vr/android/arcore/arcore.cc28
-rw-r--r--chromium/device/vr/android/arcore/arcore.h19
-rw-r--r--chromium/device/vr/android/arcore/arcore_device.cc343
-rw-r--r--chromium/device/vr/android/arcore/arcore_device.h155
-rw-r--r--chromium/device/vr/android/arcore/arcore_gl.cc1110
-rw-r--r--chromium/device/vr/android/arcore/arcore_gl.h327
-rw-r--r--chromium/device/vr/android/arcore/arcore_gl_thread.cc46
-rw-r--r--chromium/device/vr/android/arcore/arcore_gl_thread.h47
-rw-r--r--chromium/device/vr/android/arcore/arcore_impl.cc201
-rw-r--r--chromium/device/vr/android/arcore/arcore_impl.h19
-rw-r--r--chromium/device/vr/android/arcore/arcore_math_utils.cc63
-rw-r--r--chromium/device/vr/android/arcore/arcore_math_utils.h31
-rw-r--r--chromium/device/vr/android/arcore/arcore_session_utils.h54
-rw-r--r--chromium/device/vr/android/arcore/scoped_arcore_objects.h5
-rw-r--r--chromium/device/vr/android/gvr/gvr_device.cc30
-rw-r--r--chromium/device/vr/android/java/DEPS3
-rw-r--r--chromium/device/vr/android/java/src/org/chromium/device/vr/NonPresentingGvrContext.java102
-rw-r--r--chromium/device/vr/android/mailbox_to_surface_bridge.h97
-rw-r--r--chromium/device/vr/android/web_xr_presentation_state.cc184
-rw-r--r--chromium/device/vr/android/web_xr_presentation_state.h217
-rw-r--r--chromium/device/vr/buildflags/BUILD.gn1
-rw-r--r--chromium/device/vr/buildflags/buildflags.gni8
-rw-r--r--chromium/device/vr/gl_bindings.h26
-rw-r--r--chromium/device/vr/oculus/oculus_device.cc6
-rw-r--r--chromium/device/vr/openvr/OWNERS5
-rw-r--r--chromium/device/vr/openvr/openvr_api_wrapper.cc116
-rw-r--r--chromium/device/vr/openvr/openvr_api_wrapper.h55
-rw-r--r--chromium/device/vr/openvr/openvr_device.cc310
-rw-r--r--chromium/device/vr/openvr/openvr_device.h82
-rw-r--r--chromium/device/vr/openvr/openvr_gamepad_helper.cc333
-rw-r--r--chromium/device/vr/openvr/openvr_gamepad_helper.h37
-rw-r--r--chromium/device/vr/openvr/openvr_render_loop.cc324
-rw-r--r--chromium/device/vr/openvr/openvr_render_loop.h72
-rw-r--r--chromium/device/vr/openvr/openvr_type_converters.cc41
-rw-r--r--chromium/device/vr/openvr/openvr_type_converters.h20
-rw-r--r--chromium/device/vr/openxr/openxr_controller.cc16
-rw-r--r--chromium/device/vr/openxr/openxr_controller.h2
-rw-r--r--chromium/device/vr/openxr/openxr_defs.h5
-rw-r--r--chromium/device/vr/openxr/openxr_device.cc6
-rw-r--r--chromium/device/vr/openxr/openxr_input_helper.cc3
-rw-r--r--chromium/device/vr/openxr/openxr_interaction_profiles.h100
-rw-r--r--chromium/device/vr/openxr/openxr_render_loop.cc2
-rw-r--r--chromium/device/vr/openxr/openxr_util.cc19
-rw-r--r--chromium/device/vr/openxr/openxr_util.h2
-rw-r--r--chromium/device/vr/orientation/orientation_device.cc12
-rw-r--r--chromium/device/vr/orientation/orientation_device_unittest.cc12
-rw-r--r--chromium/device/vr/public/mojom/BUILD.gn3
-rw-r--r--chromium/device/vr/public/mojom/README.md2
-rw-r--r--chromium/device/vr/public/mojom/isolated_xr_service.mojom17
-rw-r--r--chromium/device/vr/public/mojom/vr_service.mojom59
-rw-r--r--chromium/device/vr/test/test_hook.h1
-rw-r--r--chromium/device/vr/vr_device.h6
-rw-r--r--chromium/device/vr/vr_device_base.cc5
-rw-r--r--chromium/device/vr/vr_device_base.h3
-rw-r--r--chromium/device/vr/vr_device_base_unittest.cc10
-rw-r--r--chromium/device/vr/vr_gl_util.cc105
-rw-r--r--chromium/device/vr/vr_gl_util.h56
-rw-r--r--chromium/device/vr/windows/compositor_base.cc25
-rw-r--r--chromium/device/vr/windows/compositor_base.h2
-rw-r--r--chromium/device/vr/windows_mixed_reality/mixed_reality_device.cc5
-rw-r--r--chromium/device/vr/windows_mixed_reality/mixed_reality_renderloop.cc2
219 files changed, 10107 insertions, 6307 deletions
diff --git a/chromium/device/BUILD.gn b/chromium/device/BUILD.gn
index 932276b8b9a..db0daed3d42 100644
--- a/chromium/device/BUILD.gn
+++ b/chromium/device/BUILD.gn
@@ -15,11 +15,20 @@ if (is_mac) {
import("//build/config/mac/mac_sdk.gni")
}
+# This file depends on the legacy global sources assignment filter. It should
+# be converted to check target platform before assigning source files to the
+# sources variable. Remove this import and set_sources_assignment_filter call
+# when the file has been converted. See https://crbug.com/1018739 for details.
+import("//build/config/deprecated_default_sources_assignment_filter.gni")
+set_sources_assignment_filter(deprecated_default_sources_assignment_filter)
+
is_linux_without_udev = (is_linux || is_chromeos) && !use_udev
test("device_unittests") {
sources = [
"base/synchronization/one_writer_seqlock_unittest.cc",
+ "bluetooth/adapter_unittest.cc",
+ "bluetooth/advertisement_unittest.cc",
"bluetooth/bluetooth_adapter_android_unittest.cc",
"bluetooth/bluetooth_adapter_mac_metrics_unittest.mm",
"bluetooth/bluetooth_adapter_mac_unittest.mm",
@@ -144,12 +153,14 @@ test("device_unittests") {
"fido/ctap_request_unittest.cc",
"fido/ctap_response_unittest.cc",
"fido/fake_fido_discovery_unittest.cc",
+ "fido/fido_device_authenticator_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/large_blob_unittest.cc",
"fido/mac/browsing_data_deletion_unittest.mm",
"fido/mac/credential_metadata_unittest.cc",
"fido/mac/get_assertion_operation_unittest_mac.mm",
diff --git a/chromium/device/base/BUILD.gn b/chromium/device/base/BUILD.gn
index d51617965f7..73e821bb367 100644
--- a/chromium/device/base/BUILD.gn
+++ b/chromium/device/base/BUILD.gn
@@ -5,6 +5,13 @@
import("//build/config/features.gni")
import("//device/vr/buildflags/buildflags.gni")
+# This file depends on the legacy global sources assignment filter. It should
+# be converted to check target platform before assigning source files to the
+# sources variable. Remove this import and set_sources_assignment_filter call
+# when the file has been converted. See https://crbug.com/1018739 for details.
+import("//build/config/deprecated_default_sources_assignment_filter.gni")
+set_sources_assignment_filter(deprecated_default_sources_assignment_filter)
+
component("base") {
output_name = "device_base"
diff --git a/chromium/device/base/features.cc b/chromium/device/base/features.cc
index 899c3269372..1f7425f1975 100644
--- a/chromium/device/base/features.cc
+++ b/chromium/device/base/features.cc
@@ -8,10 +8,15 @@
namespace device {
-#if defined(OS_MAC) || defined(OS_WIN)
+#if defined(OS_MAC)
const base::Feature kNewUsbBackend{"NewUsbBackend",
base::FEATURE_DISABLED_BY_DEFAULT};
-#endif // defined(OS_MAC) || defined(OS_WIN)
+#endif // defined(OS_MAC)
+
+#if defined(OS_WIN)
+const base::Feature kNewUsbBackend{"NewUsbBackend",
+ base::FEATURE_ENABLED_BY_DEFAULT};
+#endif // defined(OS_WIN)
#if defined(OS_WIN)
const base::Feature kNewBLEWinImplementation{"NewBLEWinImplementation",
@@ -39,16 +44,6 @@ const base::Feature kWebXrOrientationSensorDevice {
};
#endif // BUILDFLAG(ENABLE_VR)
namespace features {
-#if BUILDFLAG(ENABLE_OCULUS_VR)
-// Controls WebXR support for the Oculus Runtime.
-const base::Feature kOculusVR{"OculusVR", base::FEATURE_DISABLED_BY_DEFAULT};
-#endif // ENABLE_OCULUS_VR
-
-#if BUILDFLAG(ENABLE_OPENVR)
-// Controls WebXR support for the OpenVR Runtime.
-const base::Feature kOpenVR{"OpenVR", base::FEATURE_DISABLED_BY_DEFAULT};
-#endif // ENABLE_OPENVR
-
#if BUILDFLAG(ENABLE_OPENXR)
// Controls WebXR support for the OpenXR Runtime.
const base::Feature kOpenXR{"OpenXR", base::FEATURE_ENABLED_BY_DEFAULT};
@@ -57,7 +52,7 @@ const base::Feature kOpenXR{"OpenXR", base::FEATURE_ENABLED_BY_DEFAULT};
#if BUILDFLAG(ENABLE_WINDOWS_MR)
// Controls WebXR support for the Windows Mixed Reality Runtime.
const base::Feature kWindowsMixedReality{"WindowsMixedReality",
- base::FEATURE_ENABLED_BY_DEFAULT};
+ base::FEATURE_DISABLED_BY_DEFAULT};
#endif // ENABLE_WINDOWS_MR
} // namespace features
} // namespace device
diff --git a/chromium/device/base/features.h b/chromium/device/base/features.h
index e017e526826..1d9143d4961 100644
--- a/chromium/device/base/features.h
+++ b/chromium/device/base/features.h
@@ -28,12 +28,6 @@ DEVICE_BASE_EXPORT extern const base::Feature kWebXrOrientationSensorDevice;
// New features should be added to the device::features namespace.
namespace features {
-#if BUILDFLAG(ENABLE_OCULUS_VR)
-DEVICE_BASE_EXPORT extern const base::Feature kOculusVR;
-#endif // ENABLE_OCULUS_VR
-#if BUILDFLAG(ENABLE_OPENVR)
-DEVICE_BASE_EXPORT extern const base::Feature kOpenVR;
-#endif // ENABLE_OPENVR
#if BUILDFLAG(ENABLE_OPENXR)
DEVICE_BASE_EXPORT extern const base::Feature kOpenXR;
#endif // ENABLE_OPENXR
diff --git a/chromium/device/base/synchronization/one_writer_seqlock.cc b/chromium/device/base/synchronization/one_writer_seqlock.cc
index b3b8a71f80c..213b6301c05 100644
--- a/chromium/device/base/synchronization/one_writer_seqlock.cc
+++ b/chromium/device/base/synchronization/one_writer_seqlock.cc
@@ -8,10 +8,35 @@ namespace device {
OneWriterSeqLock::OneWriterSeqLock() : sequence_(0) {}
-base::subtle::Atomic32 OneWriterSeqLock::ReadBegin(uint32_t max_retries) const {
- base::subtle::Atomic32 version;
+void OneWriterSeqLock::AtomicWriterMemcpy(void* dest,
+ const void* src,
+ size_t size) {
+ DCHECK(!(reinterpret_cast<std::uintptr_t>(dest) % 4));
+ DCHECK(!(reinterpret_cast<std::uintptr_t>(src) % 4));
+ DCHECK(size % 4 == 0);
+ for (size_t i = 0; i < size / 4; ++i) {
+ reinterpret_cast<std::atomic<int32_t>*>(dest)[i].store(
+ reinterpret_cast<const int32_t*>(src)[i], std::memory_order_relaxed);
+ }
+}
+
+void OneWriterSeqLock::AtomicReaderMemcpy(void* dest,
+ const void* src,
+ size_t size) {
+ DCHECK(!(reinterpret_cast<std::uintptr_t>(dest) % 4));
+ DCHECK(!(reinterpret_cast<std::uintptr_t>(src) % 4));
+ DCHECK(size % 4 == 0);
+ for (size_t i = 0; i < size / 4; ++i) {
+ reinterpret_cast<int32_t*>(dest)[i] =
+ reinterpret_cast<const std::atomic<int32_t>*>(src)[i].load(
+ std::memory_order_relaxed);
+ }
+}
+
+int32_t OneWriterSeqLock::ReadBegin(uint32_t max_retries) const {
+ int32_t version;
for (uint32_t i = 0; i <= max_retries; ++i) {
- version = base::subtle::Acquire_Load(&sequence_);
+ version = sequence_.load(std::memory_order_acquire);
// If the counter is even, then the associated data might be in a
// consistent state, so we can try to read.
@@ -27,16 +52,19 @@ base::subtle::Atomic32 OneWriterSeqLock::ReadBegin(uint32_t max_retries) const {
return version;
}
-bool OneWriterSeqLock::ReadRetry(base::subtle::Atomic32 version) const {
+bool OneWriterSeqLock::ReadRetry(int32_t version) const {
// If the sequence number was updated then a read should be re-attempted.
// -- Load fence, read membarrier
- return base::subtle::Release_Load(&sequence_) != version;
+ atomic_thread_fence(std::memory_order_acquire);
+ return sequence_.load(std::memory_order_relaxed) != version;
}
void OneWriterSeqLock::WriteBegin() {
// Increment the sequence number to odd to indicate the beginning of a write
// update.
- base::subtle::Barrier_AtomicIncrement(&sequence_, 1);
+ int32_t version = sequence_.fetch_add(1, std::memory_order_relaxed);
+ atomic_thread_fence(std::memory_order_release);
+ DCHECK((version & 1) == 0);
// -- Store fence, write membarrier
}
@@ -44,7 +72,8 @@ void OneWriterSeqLock::WriteEnd() {
// Increment the sequence to an even number to indicate the completion of
// a write update.
// -- Store fence, write membarrier
- base::subtle::Barrier_AtomicIncrement(&sequence_, 1);
+ int32_t version = sequence_.fetch_add(1, std::memory_order_release);
+ DCHECK((version & 1) != 0);
}
} // namespace device
diff --git a/chromium/device/base/synchronization/one_writer_seqlock.h b/chromium/device/base/synchronization/one_writer_seqlock.h
index 0bd3e89b5f8..2ad75e8af57 100644
--- a/chromium/device/base/synchronization/one_writer_seqlock.h
+++ b/chromium/device/base/synchronization/one_writer_seqlock.h
@@ -5,6 +5,8 @@
#ifndef DEVICE_BASE_SYNCHRONIZATION_ONE_WRITER_SEQLOCK_H_
#define DEVICE_BASE_SYNCHRONIZATION_ONE_WRITER_SEQLOCK_H_
+#include <atomic>
+
#include "base/atomicops.h"
#include "base/macros.h"
#include "base/threading/platform_thread.h"
@@ -36,16 +38,22 @@ namespace device {
class OneWriterSeqLock {
public:
OneWriterSeqLock();
+ // Copies data from src into dest using atomic stores. This should be used by
+ // writer of SeqLock. Data must be 4-byte aligned.
+ static void AtomicWriterMemcpy(void* dest, const void* src, size_t size);
+ // Copies data from src into dest using atomic loads. This should be used by
+ // readers of SeqLock. Data must be 4-byte aligned.
+ static void AtomicReaderMemcpy(void* dest, const void* src, size_t size);
// ReadBegin returns |sequence_| when it is even, or when it has retried
// |max_retries| times. Omitting |max_retries| results in ReadBegin not
// returning until |sequence_| is even.
- base::subtle::Atomic32 ReadBegin(uint32_t max_retries = UINT32_MAX) const;
- bool ReadRetry(base::subtle::Atomic32 version) const;
+ int32_t ReadBegin(uint32_t max_retries = UINT32_MAX) const;
+ bool ReadRetry(int32_t version) const;
void WriteBegin();
void WriteEnd();
private:
- base::subtle::Atomic32 sequence_;
+ std::atomic<int32_t> sequence_;
DISALLOW_COPY_AND_ASSIGN(OneWriterSeqLock);
};
diff --git a/chromium/device/base/synchronization/one_writer_seqlock_unittest.cc b/chromium/device/base/synchronization/one_writer_seqlock_unittest.cc
index fe88c42f693..938711c986d 100644
--- a/chromium/device/base/synchronization/one_writer_seqlock_unittest.cc
+++ b/chromium/device/base/synchronization/one_writer_seqlock_unittest.cc
@@ -5,8 +5,8 @@
#include "device/base/synchronization/one_writer_seqlock.h"
#include <stdlib.h>
+#include <atomic>
-#include "base/atomic_ref_count.h"
#include "base/macros.h"
#include "base/third_party/dynamic_annotations/dynamic_annotations.h"
#include "base/threading/platform_thread.h"
@@ -18,7 +18,8 @@ namespace device {
// Basic test to make sure that basic operation works correctly.
struct TestData {
- unsigned a, b, c;
+ // Data copies larger than a cache line.
+ uint32_t buffer[32];
};
class BasicSeqLockTestThread : public base::PlatformThread::Delegate {
@@ -27,13 +28,13 @@ class BasicSeqLockTestThread : public base::PlatformThread::Delegate {
void Init(OneWriterSeqLock* seqlock,
TestData* data,
- base::AtomicRefCount* ready) {
+ std::atomic<int>* ready) {
seqlock_ = seqlock;
data_ = data;
ready_ = ready;
}
void ThreadMain() override {
- while (ready_->IsZero()) {
+ while (!*ready_) {
base::PlatformThread::YieldCurrentThread();
}
@@ -42,24 +43,54 @@ class BasicSeqLockTestThread : public base::PlatformThread::Delegate {
base::subtle::Atomic32 version;
do {
version = seqlock_->ReadBegin();
- copy = *data_;
+ OneWriterSeqLock::AtomicReaderMemcpy(&copy, data_, sizeof(TestData));
} while (seqlock_->ReadRetry(version));
- EXPECT_EQ(copy.a + 100, copy.b);
- EXPECT_EQ(copy.c, copy.b + copy.a);
+ for (unsigned j = 1; j < 32; ++j)
+ EXPECT_EQ(copy.buffer[j], copy.buffer[0] + copy.buffer[j - 1]);
}
- ready_->Decrement();
+ --(*ready_);
}
private:
OneWriterSeqLock* seqlock_;
TestData* data_;
- base::AtomicRefCount* ready_;
+ std::atomic<int>* ready_;
DISALLOW_COPY_AND_ASSIGN(BasicSeqLockTestThread);
};
+class MaxRetriesSeqLockTestThread : public base::PlatformThread::Delegate {
+ public:
+ MaxRetriesSeqLockTestThread() = default;
+
+ void Init(OneWriterSeqLock* seqlock, std::atomic<int>* ready) {
+ seqlock_ = seqlock;
+ ready_ = ready;
+ }
+ void ThreadMain() override {
+ while (!*ready_) {
+ base::PlatformThread::YieldCurrentThread();
+ }
+
+ for (unsigned i = 0; i < 10; ++i) {
+ base::subtle::Atomic32 version;
+ version = seqlock_->ReadBegin(100);
+
+ EXPECT_NE(version & 1, 0);
+ }
+
+ --*ready_;
+ }
+
+ private:
+ OneWriterSeqLock* seqlock_;
+ std::atomic<int>* ready_;
+
+ DISALLOW_COPY_AND_ASSIGN(MaxRetriesSeqLockTestThread);
+};
+
#if defined(OS_ANDROID)
#define MAYBE_ManyThreads FLAKY_ManyThreads
#else
@@ -67,8 +98,8 @@ class BasicSeqLockTestThread : public base::PlatformThread::Delegate {
#endif
TEST(OneWriterSeqLockTest, MAYBE_ManyThreads) {
OneWriterSeqLock seqlock;
- TestData data = {0, 0, 0};
- base::AtomicRefCount ready(0);
+ TestData data;
+ std::atomic<int> ready(0);
ANNOTATE_BENIGN_RACE_SIZED(&data, sizeof(data), "Racey reads are discarded");
@@ -76,24 +107,27 @@ TEST(OneWriterSeqLockTest, MAYBE_ManyThreads) {
BasicSeqLockTestThread threads[kNumReaderThreads];
base::PlatformThreadHandle handles[kNumReaderThreads];
- for (unsigned i = 0; i < kNumReaderThreads; ++i)
+ for (uint32_t i = 0; i < kNumReaderThreads; ++i)
threads[i].Init(&seqlock, &data, &ready);
- for (unsigned i = 0; i < kNumReaderThreads; ++i)
+ for (uint32_t i = 0; i < kNumReaderThreads; ++i)
ASSERT_TRUE(base::PlatformThread::Create(0, &threads[i], &handles[i]));
// The main thread is the writer, and the spawned are readers.
- unsigned counter = 0;
+ uint32_t counter = 0;
for (;;) {
+ TestData new_data;
+ new_data.buffer[0] = counter++;
+ for (unsigned i = 1; i < 32; ++i) {
+ new_data.buffer[i] = new_data.buffer[0] + new_data.buffer[i - 1];
+ }
seqlock.WriteBegin();
- data.a = counter++;
- data.b = data.a + 100;
- data.c = data.b + data.a;
+ OneWriterSeqLock::AtomicWriterMemcpy(&data, &new_data, sizeof(TestData));
seqlock.WriteEnd();
if (counter == 1)
- ready.Increment(kNumReaderThreads);
+ ready += kNumReaderThreads;
- if (ready.IsZero())
+ if (!ready)
break;
}
@@ -101,4 +135,29 @@ TEST(OneWriterSeqLockTest, MAYBE_ManyThreads) {
base::PlatformThread::Join(handles[i]);
}
+TEST(OneWriterSeqLockTest, MaxRetries) {
+ OneWriterSeqLock seqlock;
+ std::atomic<int> ready(0);
+
+ static const unsigned kNumReaderThreads = 3;
+ MaxRetriesSeqLockTestThread threads[kNumReaderThreads];
+ base::PlatformThreadHandle handles[kNumReaderThreads];
+
+ for (uint32_t i = 0; i < kNumReaderThreads; ++i)
+ threads[i].Init(&seqlock, &ready);
+ for (uint32_t i = 0; i < kNumReaderThreads; ++i)
+ ASSERT_TRUE(base::PlatformThread::Create(0, &threads[i], &handles[i]));
+
+ // The main thread is the writer, and the spawned are readers.
+ seqlock.WriteBegin();
+ ready += kNumReaderThreads;
+ while (ready) {
+ base::PlatformThread::YieldCurrentThread();
+ }
+ seqlock.WriteEnd();
+
+ for (unsigned i = 0; i < kNumReaderThreads; ++i)
+ base::PlatformThread::Join(handles[i]);
+}
+
} // namespace device
diff --git a/chromium/device/bluetooth/BUILD.gn b/chromium/device/bluetooth/BUILD.gn
index e4c977b4895..40539eb8592 100644
--- a/chromium/device/bluetooth/BUILD.gn
+++ b/chromium/device/bluetooth/BUILD.gn
@@ -12,6 +12,13 @@ if (is_chromeos) {
use_real_dbus_clients = false
}
+# This file depends on the legacy global sources assignment filter. It should
+# be converted to check target platform before assigning source files to the
+# sources variable. Remove this import and set_sources_assignment_filter call
+# when the file has been converted. See https://crbug.com/1018739 for details.
+import("//build/config/deprecated_default_sources_assignment_filter.gni")
+set_sources_assignment_filter(deprecated_default_sources_assignment_filter)
+
config("bluetooth_config") {
if (is_win) {
ldflags = [
@@ -36,6 +43,8 @@ source_set("deprecated_experimental_mojo") {
"//device/bluetooth/public/mojom/gatt_result_type_converter.h",
"adapter.cc",
"adapter.h",
+ "advertisement.cc",
+ "advertisement.h",
"device.cc",
"device.h",
"discovery_session.cc",
@@ -107,8 +116,6 @@ component("bluetooth") {
"bluetooth_adapter_android.h",
"bluetooth_adapter_factory.cc",
"bluetooth_adapter_factory.h",
- "bluetooth_adapter_factory_wrapper.cc",
- "bluetooth_adapter_factory_wrapper.h",
"bluetooth_adapter_mac.h",
"bluetooth_adapter_mac.mm",
"bluetooth_adapter_mac_metrics.h",
diff --git a/chromium/device/bluetooth/adapter.cc b/chromium/device/bluetooth/adapter.cc
index dad05519932..13f0a70b818 100644
--- a/chromium/device/bluetooth/adapter.cc
+++ b/chromium/device/bluetooth/adapter.cc
@@ -12,6 +12,7 @@
#include "base/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "build/build_config.h"
+#include "device/bluetooth/advertisement.h"
#include "device/bluetooth/bluetooth_socket.h"
#include "device/bluetooth/device.h"
#include "device/bluetooth/discovery_session.h"
@@ -20,6 +21,7 @@
#include "device/bluetooth/server_socket.h"
#include "device/bluetooth/socket.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
namespace bluetooth {
@@ -79,12 +81,37 @@ void Adapter::GetInfo(GetInfoCallback callback) {
std::move(callback).Run(std::move(adapter_info));
}
-void Adapter::SetClient(mojo::PendingRemote<mojom::AdapterClient> client,
- SetClientCallback callback) {
- client_.Bind(std::move(client));
+void Adapter::AddObserver(mojo::PendingRemote<mojom::AdapterObserver> observer,
+ AddObserverCallback callback) {
+ observers_.Add(std::move(observer));
std::move(callback).Run();
}
+void Adapter::RegisterAdvertisement(const device::BluetoothUUID& service_uuid,
+ const std::vector<uint8_t>& service_data,
+ RegisterAdvertisementCallback callback) {
+ auto advertisement_data =
+ std::make_unique<device::BluetoothAdvertisement::Data>(
+ device::BluetoothAdvertisement::ADVERTISEMENT_TYPE_BROADCAST);
+
+ auto uuid_list = std::make_unique<device::BluetoothAdvertisement::UUIDList>();
+ uuid_list->push_back(service_uuid.value());
+ advertisement_data->set_service_uuids(std::move(uuid_list));
+
+ auto service_data_map =
+ std::make_unique<device::BluetoothAdvertisement::ServiceData>();
+ service_data_map->emplace(service_uuid.value(), service_data);
+ advertisement_data->set_service_data(std::move(service_data_map));
+
+ auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback));
+ adapter_->RegisterAdvertisement(
+ std::move(advertisement_data),
+ base::BindOnce(&Adapter::OnRegisterAdvertisement,
+ weak_ptr_factory_.GetWeakPtr(), copyable_callback),
+ base::BindOnce(&Adapter::OnRegisterAdvertisementError,
+ weak_ptr_factory_.GetWeakPtr(), copyable_callback));
+}
+
void Adapter::SetDiscoverable(bool discoverable,
SetDiscoverableCallback callback) {
auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback));
@@ -132,7 +159,7 @@ void Adapter::CreateRfcommService(const std::string& service_name,
const device::BluetoothUUID& service_uuid,
CreateRfcommServiceCallback callback) {
device::BluetoothAdapter::ServiceOptions service_options;
- service_options.name = std::make_unique<std::string>(service_name);
+ service_options.name = service_name;
auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback));
adapter_->CreateRfcommService(
@@ -145,50 +172,47 @@ void Adapter::CreateRfcommService(const std::string& service_name,
void Adapter::AdapterPresentChanged(device::BluetoothAdapter* adapter,
bool present) {
- if (client_)
- client_->PresentChanged(present);
+ for (auto& observer : observers_)
+ observer->PresentChanged(present);
}
void Adapter::AdapterPoweredChanged(device::BluetoothAdapter* adapter,
bool powered) {
- if (client_)
- client_->PoweredChanged(powered);
+ for (auto& observer : observers_)
+ observer->PoweredChanged(powered);
}
void Adapter::AdapterDiscoverableChanged(device::BluetoothAdapter* adapter,
bool discoverable) {
- if (client_)
- client_->DiscoverableChanged(discoverable);
+ for (auto& observer : observers_)
+ observer->DiscoverableChanged(discoverable);
}
void Adapter::AdapterDiscoveringChanged(device::BluetoothAdapter* adapter,
bool discovering) {
- if (client_)
- client_->DiscoveringChanged(discovering);
+ for (auto& observer : observers_)
+ observer->DiscoveringChanged(discovering);
}
void Adapter::DeviceAdded(device::BluetoothAdapter* adapter,
device::BluetoothDevice* device) {
- if (client_) {
- auto device_info = Device::ConstructDeviceInfoStruct(device);
- client_->DeviceAdded(std::move(device_info));
- }
+ auto device_info = Device::ConstructDeviceInfoStruct(device);
+ for (auto& observer : observers_)
+ observer->DeviceAdded(device_info->Clone());
}
void Adapter::DeviceChanged(device::BluetoothAdapter* adapter,
device::BluetoothDevice* device) {
- if (client_) {
- auto device_info = Device::ConstructDeviceInfoStruct(device);
- client_->DeviceChanged(std::move(device_info));
- }
+ auto device_info = Device::ConstructDeviceInfoStruct(device);
+ for (auto& observer : observers_)
+ observer->DeviceChanged(device_info->Clone());
}
void Adapter::DeviceRemoved(device::BluetoothAdapter* adapter,
device::BluetoothDevice* device) {
- if (client_) {
- auto device_info = Device::ConstructDeviceInfoStruct(device);
- client_->DeviceRemoved(std::move(device_info));
- }
+ auto device_info = Device::ConstructDeviceInfoStruct(device);
+ for (auto& observer : observers_)
+ observer->DeviceRemoved(device_info->Clone());
}
void Adapter::OnGattConnected(
@@ -207,6 +231,23 @@ void Adapter::OnConnectError(
/*device=*/mojo::NullRemote());
}
+void Adapter::OnRegisterAdvertisement(
+ RegisterAdvertisementCallback callback,
+ scoped_refptr<device::BluetoothAdvertisement> advertisement) {
+ mojo::PendingRemote<mojom::Advertisement> pending_advertisement;
+ mojo::MakeSelfOwnedReceiver(
+ std::make_unique<Advertisement>(std::move(advertisement)),
+ pending_advertisement.InitWithNewPipeAndPassReceiver());
+ std::move(callback).Run(std::move(pending_advertisement));
+}
+
+void Adapter::OnRegisterAdvertisementError(
+ RegisterAdvertisementCallback callback,
+ device::BluetoothAdvertisement::ErrorCode error_code) {
+ DLOG(ERROR) << "Failed to register advertisement, error code: " << error_code;
+ std::move(callback).Run(/*advertisement=*/mojo::NullRemote());
+}
+
void Adapter::OnSetDiscoverable(SetDiscoverableCallback callback) {
std::move(callback).Run(/*success=*/true);
}
diff --git a/chromium/device/bluetooth/adapter.h b/chromium/device/bluetooth/adapter.h
index 329f95ad31a..f9d113afe42 100644
--- a/chromium/device/bluetooth/adapter.h
+++ b/chromium/device/bluetooth/adapter.h
@@ -11,12 +11,13 @@
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_advertisement.h"
#include "device/bluetooth/bluetooth_gatt_connection.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
#include "device/bluetooth/public/mojom/adapter.mojom.h"
#include "device/bluetooth/public/mojom/device.mojom-forward.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
-#include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/bindings/remote_set.h"
namespace bluetooth {
@@ -35,8 +36,11 @@ class Adapter : public mojom::Adapter,
ConnectToDeviceCallback callback) override;
void GetDevices(GetDevicesCallback callback) override;
void GetInfo(GetInfoCallback callback) override;
- void SetClient(mojo::PendingRemote<mojom::AdapterClient> client,
- SetClientCallback callback) override;
+ void AddObserver(mojo::PendingRemote<mojom::AdapterObserver> observer,
+ AddObserverCallback callback) override;
+ void RegisterAdvertisement(const device::BluetoothUUID& service_uuid,
+ const std::vector<uint8_t>& service_data,
+ RegisterAdvertisementCallback callback) override;
void SetDiscoverable(bool discoverable,
SetDiscoverableCallback callback) override;
void SetName(const std::string& name, SetNameCallback callback) override;
@@ -74,6 +78,13 @@ class Adapter : public mojom::Adapter,
void OnConnectError(ConnectToDeviceCallback callback,
device::BluetoothDevice::ConnectErrorCode error_code);
+ void OnRegisterAdvertisement(
+ RegisterAdvertisementCallback callback,
+ scoped_refptr<device::BluetoothAdvertisement> advertisement);
+ void OnRegisterAdvertisementError(
+ RegisterAdvertisementCallback callback,
+ device::BluetoothAdvertisement::ErrorCode error_code);
+
void OnSetDiscoverable(SetDiscoverableCallback callback);
void OnSetDiscoverableError(SetDiscoverableCallback callback);
@@ -98,8 +109,8 @@ class Adapter : public mojom::Adapter,
// The current Bluetooth adapter.
scoped_refptr<device::BluetoothAdapter> adapter_;
- // The adapter client that listens to this service.
- mojo::Remote<mojom::AdapterClient> client_;
+ // The adapter observers that listen to this service.
+ mojo::RemoteSet<mojom::AdapterObserver> observers_;
base::WeakPtrFactory<Adapter> weak_ptr_factory_{this};
diff --git a/chromium/device/bluetooth/adapter_unittest.cc b/chromium/device/bluetooth/adapter_unittest.cc
new file mode 100644
index 00000000000..e6e98d09a29
--- /dev/null
+++ b/chromium/device/bluetooth/adapter_unittest.cc
@@ -0,0 +1,126 @@
+// 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/bluetooth/adapter.h"
+
+#include "base/callback_helpers.h"
+#include "base/run_loop.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/task_environment.h"
+#include "device/bluetooth/bluetooth_advertisement.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "device/bluetooth/test/mock_bluetooth_advertisement.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using testing::NiceMock;
+using testing::Return;
+
+namespace {
+
+const char kServiceId[] = "00000000-0000-0000-0000-000000000001";
+const char kDeviceServiceDataStr[] = "ServiceData";
+
+std::vector<uint8_t> GetByteVector(const std::string& str) {
+ return std::vector<uint8_t>(str.begin(), str.end());
+}
+
+class MockBluetoothAdapterWithAdvertisements
+ : public device::MockBluetoothAdapter {
+ public:
+ void RegisterAdvertisement(
+ std::unique_ptr<device::BluetoothAdvertisement::Data> advertisement_data,
+ device::BluetoothAdapter::CreateAdvertisementCallback callback,
+ device::BluetoothAdapter::AdvertisementErrorCallback error_callback)
+ override {
+ last_register_advertisement_args_ =
+ std::make_pair(*advertisement_data->service_uuids(),
+ *advertisement_data->service_data());
+
+ if (should_advertisement_registration_succeed_) {
+ std::move(callback).Run(
+ base::MakeRefCounted<device::MockBluetoothAdvertisement>());
+ } else {
+ std::move(error_callback)
+ .Run(device::BluetoothAdvertisement::ErrorCode::
+ INVALID_ADVERTISEMENT_ERROR_CODE);
+ }
+ }
+
+ bool should_advertisement_registration_succeed_ = true;
+ base::Optional<std::pair<device::BluetoothAdvertisement::UUIDList,
+ device::BluetoothAdvertisement::ServiceData>>
+ last_register_advertisement_args_;
+
+ protected:
+ ~MockBluetoothAdapterWithAdvertisements() override = default;
+};
+
+} // namespace
+
+namespace bluetooth {
+
+class AdapterTest : public testing::Test {
+ public:
+ AdapterTest() = default;
+ ~AdapterTest() override = default;
+ AdapterTest(const AdapterTest&) = delete;
+ AdapterTest& operator=(const AdapterTest&) = delete;
+
+ void SetUp() override {
+ mock_bluetooth_adapter_ = base::MakeRefCounted<
+ NiceMock<MockBluetoothAdapterWithAdvertisements>>();
+ ON_CALL(*mock_bluetooth_adapter_, IsPresent()).WillByDefault(Return(true));
+ ON_CALL(*mock_bluetooth_adapter_, IsPowered()).WillByDefault(Return(true));
+
+ adapter_ = std::make_unique<Adapter>(mock_bluetooth_adapter_);
+ }
+
+ protected:
+ void VerifyRegisterAdvertisement(bool should_succeed) {
+ mock_bluetooth_adapter_->should_advertisement_registration_succeed_ =
+ should_succeed;
+
+ auto service_data = GetByteVector(kDeviceServiceDataStr);
+ mojo::Remote<mojom::Advertisement> advertisement;
+
+ base::RunLoop run_loop;
+ adapter_->RegisterAdvertisement(
+ device::BluetoothUUID(kServiceId), service_data,
+ base::BindLambdaForTesting([&](mojo::PendingRemote<mojom::Advertisement>
+ pending_advertisement) {
+ EXPECT_EQ(should_succeed, pending_advertisement.is_valid());
+ run_loop.Quit();
+ }));
+ run_loop.Run();
+
+ auto& uuid_list =
+ mock_bluetooth_adapter_->last_register_advertisement_args_->first;
+ EXPECT_EQ(1u, uuid_list.size());
+ EXPECT_EQ(kServiceId, uuid_list[0]);
+ EXPECT_EQ(
+ service_data,
+ mock_bluetooth_adapter_->last_register_advertisement_args_->second.at(
+ kServiceId));
+ }
+
+ scoped_refptr<NiceMock<MockBluetoothAdapterWithAdvertisements>>
+ mock_bluetooth_adapter_;
+ std::unique_ptr<Adapter> adapter_;
+
+ private:
+ base::test::TaskEnvironment task_environment_;
+};
+
+TEST_F(AdapterTest, TestRegisterAdvertisement_Success) {
+ VerifyRegisterAdvertisement(/*should_succeed=*/true);
+}
+
+TEST_F(AdapterTest, TestRegisterAdvertisement_Error) {
+ VerifyRegisterAdvertisement(/*should_succeed=*/false);
+}
+
+} // namespace bluetooth
diff --git a/chromium/device/bluetooth/advertisement.cc b/chromium/device/bluetooth/advertisement.cc
new file mode 100644
index 00000000000..cdb826529a1
--- /dev/null
+++ b/chromium/device/bluetooth/advertisement.cc
@@ -0,0 +1,46 @@
+// 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/bluetooth/advertisement.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+
+namespace bluetooth {
+
+Advertisement::Advertisement(
+ scoped_refptr<device::BluetoothAdvertisement> bluetooth_advertisement)
+ : bluetooth_advertisement_(std::move(bluetooth_advertisement)) {}
+
+Advertisement::~Advertisement() {
+ Unregister(base::DoNothing());
+}
+
+void Advertisement::Unregister(UnregisterCallback callback) {
+ if (!bluetooth_advertisement_)
+ return;
+
+ auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback));
+ bluetooth_advertisement_->Unregister(
+ base::BindOnce(&Advertisement::OnUnregister,
+ weak_ptr_factory_.GetWeakPtr(), copyable_callback),
+ base::BindOnce(&Advertisement::OnUnregisterError,
+ weak_ptr_factory_.GetWeakPtr(), copyable_callback));
+}
+
+void Advertisement::OnUnregister(UnregisterCallback callback) {
+ bluetooth_advertisement_.reset();
+ std::move(callback).Run();
+}
+
+void Advertisement::OnUnregisterError(
+ UnregisterCallback callback,
+ device::BluetoothAdvertisement::ErrorCode error_code) {
+ DLOG(ERROR) << "Failed to unregister advertisement, error code: "
+ << error_code;
+ bluetooth_advertisement_.reset();
+ std::move(callback).Run();
+}
+
+} // namespace bluetooth
diff --git a/chromium/device/bluetooth/advertisement.h b/chromium/device/bluetooth/advertisement.h
new file mode 100644
index 00000000000..7746681d357
--- /dev/null
+++ b/chromium/device/bluetooth/advertisement.h
@@ -0,0 +1,43 @@
+// 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_BLUETOOTH_ADVERTISEMENT_H_
+#define DEVICE_BLUETOOTH_ADVERTISEMENT_H_
+
+#include "base/memory/ref_counted.h"
+#include "device/bluetooth/bluetooth_advertisement.h"
+#include "device/bluetooth/public/mojom/adapter.mojom.h"
+
+namespace bluetooth {
+
+// Implementation of Mojo Advertisement in
+// device/bluetooth/public/mojom/adapter.mojom.
+// Uses the platform abstraction of //device/bluetooth.
+// An instance of this class is constructed by Adapter and strongly bound to its
+// MessagePipe. When the instance is destroyed, the underlying
+// BluetoothAdvertisement is destroyed.
+class Advertisement : public mojom::Advertisement {
+ public:
+ explicit Advertisement(
+ scoped_refptr<device::BluetoothAdvertisement> bluetooth_advertisement);
+ ~Advertisement() override;
+ Advertisement(const Advertisement&) = delete;
+ Advertisement& operator=(const Advertisement&) = delete;
+
+ // mojom::Advertisement:
+ void Unregister(UnregisterCallback callback) override;
+
+ private:
+ void OnUnregister(UnregisterCallback callback);
+ void OnUnregisterError(UnregisterCallback callback,
+ device::BluetoothAdvertisement::ErrorCode error_code);
+
+ scoped_refptr<device::BluetoothAdvertisement> bluetooth_advertisement_;
+
+ base::WeakPtrFactory<Advertisement> weak_ptr_factory_{this};
+};
+
+} // namespace bluetooth
+
+#endif // DEVICE_BLUETOOTH_ADVERTISEMENT_H_
diff --git a/chromium/device/bluetooth/advertisement_unittest.cc b/chromium/device/bluetooth/advertisement_unittest.cc
new file mode 100644
index 00000000000..2dd863c6da2
--- /dev/null
+++ b/chromium/device/bluetooth/advertisement_unittest.cc
@@ -0,0 +1,73 @@
+// 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/bluetooth/advertisement.h"
+
+#include "base/run_loop.h"
+#include "base/test/task_environment.h"
+#include "device/bluetooth/bluetooth_advertisement.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class FakeBluetoothAdvertisement : public device::BluetoothAdvertisement {
+ public:
+ // device::BluetoothAdvertisement:
+ void Unregister(SuccessCallback success_callback,
+ ErrorCallback error_callback) override {
+ called_unregister_ = true;
+ std::move(success_callback).Run();
+ }
+
+ bool called_unregister() { return called_unregister_; }
+
+ private:
+ ~FakeBluetoothAdvertisement() override = default;
+
+ bool called_unregister_ = false;
+};
+
+} // namespace
+
+namespace bluetooth {
+
+class AdvertisementTest : public testing::Test {
+ public:
+ AdvertisementTest() = default;
+ ~AdvertisementTest() override = default;
+ AdvertisementTest(const AdvertisementTest&) = delete;
+ AdvertisementTest& operator=(const AdvertisementTest&) = delete;
+
+ void SetUp() override {
+ fake_bluetooth_advertisement_ =
+ base::MakeRefCounted<FakeBluetoothAdvertisement>();
+ advertisement_ =
+ std::make_unique<Advertisement>(fake_bluetooth_advertisement_);
+ }
+
+ protected:
+ scoped_refptr<FakeBluetoothAdvertisement> fake_bluetooth_advertisement_;
+ std::unique_ptr<Advertisement> advertisement_;
+
+ private:
+ base::test::TaskEnvironment task_environment_;
+};
+
+TEST_F(AdvertisementTest, TestOnDestroyCallsUnregister) {
+ // When destroyed, |advertisement_| is expected to tear down its
+ // BluetoothAdvertisement.
+ ASSERT_FALSE(fake_bluetooth_advertisement_->called_unregister());
+ advertisement_.reset();
+ EXPECT_TRUE(fake_bluetooth_advertisement_->called_unregister());
+}
+
+TEST_F(AdvertisementTest, TestUnregister) {
+ ASSERT_FALSE(fake_bluetooth_advertisement_->called_unregister());
+ base::RunLoop run_loop;
+ advertisement_->Unregister(run_loop.QuitClosure());
+ run_loop.Run();
+ EXPECT_TRUE(fake_bluetooth_advertisement_->called_unregister());
+}
+
+} // namespace bluetooth
diff --git a/chromium/device/bluetooth/android/java/DEPS b/chromium/device/bluetooth/android/java/DEPS
deleted file mode 100644
index 53896d8aa7c..00000000000
--- a/chromium/device/bluetooth/android/java/DEPS
+++ /dev/null
@@ -1,3 +0,0 @@
-include_rules = [
- "+components/location/android/java",
-]
diff --git a/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothAdapter.java b/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothAdapter.java
deleted file mode 100644
index 4b782be0520..00000000000
--- a/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothAdapter.java
+++ /dev/null
@@ -1,370 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.device.bluetooth;
-
-import android.annotation.TargetApi;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.le.ScanFilter;
-import android.bluetooth.le.ScanSettings;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Build;
-import android.os.ParcelUuid;
-import android.util.SparseArray;
-
-import org.chromium.base.Log;
-import org.chromium.base.annotations.CalledByNative;
-import org.chromium.base.annotations.JNIAdditionalImport;
-import org.chromium.base.annotations.JNINamespace;
-import org.chromium.base.annotations.NativeMethods;
-import org.chromium.components.location.LocationUtils;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * Exposes android.bluetooth.BluetoothAdapter as necessary for C++
- * device::BluetoothAdapterAndroid, which implements the cross platform
- * device::BluetoothAdapter.
- *
- * Lifetime is controlled by device::BluetoothAdapterAndroid.
- */
-@JNINamespace("device")
-@JNIAdditionalImport(Wrappers.class)
-@TargetApi(Build.VERSION_CODES.M)
-final class ChromeBluetoothAdapter extends BroadcastReceiver {
- private static final String TAG = "Bluetooth";
-
- private long mNativeBluetoothAdapterAndroid;
- // mAdapter is final to ensure registerReceiver is followed by unregisterReceiver.
- private final Wrappers.BluetoothAdapterWrapper mAdapter;
- private ScanCallback mScanCallback;
-
- // ---------------------------------------------------------------------------------------------
- // Construction and handler for C++ object destruction.
-
- /**
- * Constructs a ChromeBluetoothAdapter.
- * @param nativeBluetoothAdapterAndroid Is the associated C++
- * BluetoothAdapterAndroid pointer value.
- * @param adapterWrapper Wraps the default android.bluetooth.BluetoothAdapter,
- * but may be either null if an adapter is not available
- * or a fake for testing.
- */
- public ChromeBluetoothAdapter(
- long nativeBluetoothAdapterAndroid, Wrappers.BluetoothAdapterWrapper adapterWrapper) {
- mNativeBluetoothAdapterAndroid = nativeBluetoothAdapterAndroid;
- mAdapter = adapterWrapper;
- registerBroadcastReceiver();
- if (adapterWrapper == null) {
- Log.i(TAG, "ChromeBluetoothAdapter created with no adapterWrapper.");
- } else {
- Log.i(TAG, "ChromeBluetoothAdapter created with provided adapterWrapper.");
- }
- }
-
- /**
- * Handles C++ object being destroyed.
- */
- @CalledByNative
- private void onBluetoothAdapterAndroidDestruction() {
- stopScan();
- mNativeBluetoothAdapterAndroid = 0;
- unregisterBroadcastReceiver();
- }
-
- // ---------------------------------------------------------------------------------------------
- // BluetoothAdapterAndroid methods implemented in java:
-
- // Implements BluetoothAdapterAndroid::Create.
- @CalledByNative
- private static ChromeBluetoothAdapter create(
- long nativeBluetoothAdapterAndroid, Wrappers.BluetoothAdapterWrapper adapterWrapper) {
- return new ChromeBluetoothAdapter(nativeBluetoothAdapterAndroid, adapterWrapper);
- }
-
- // Implements BluetoothAdapterAndroid::GetAddress.
- @CalledByNative
- private String getAddress() {
- if (isPresent()) {
- return mAdapter.getAddress();
- } else {
- return "";
- }
- }
-
- // Implements BluetoothAdapterAndroid::GetName.
- @CalledByNative
- private String getName() {
- if (isPresent()) {
- return mAdapter.getName();
- } else {
- return "";
- }
- }
-
- // Implements BluetoothAdapterAndroid::IsPresent.
- @CalledByNative
- private boolean isPresent() {
- return mAdapter != null;
- }
-
- // Implements BluetoothAdapterAndroid::IsPowered.
- @CalledByNative
- private boolean isPowered() {
- return isPresent() && mAdapter.isEnabled();
- }
-
- // Implements BluetoothAdapterAndroid::SetPowered.
- @CalledByNative
- private boolean setPowered(boolean powered) {
- if (powered) {
- return isPresent() && mAdapter.enable();
- } else {
- return isPresent() && mAdapter.disable();
- }
- }
-
- // Implements BluetoothAdapterAndroid::IsDiscoverable.
- @CalledByNative
- private boolean isDiscoverable() {
- return isPresent()
- && mAdapter.getScanMode() == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
- }
-
- // Implements BluetoothAdapterAndroid::IsDiscovering.
- @CalledByNative
- private boolean isDiscovering() {
- return isPresent() && (mAdapter.isDiscovering() || mScanCallback != null);
- }
-
- /**
- * Starts a Low Energy scan.
- * @param filters List of filters used to minimize number of devices returned
- * @return True on success.
- */
- @CalledByNative
- private boolean startScan(List<ScanFilter> filters) {
- Wrappers.BluetoothLeScannerWrapper scanner = mAdapter.getBluetoothLeScanner();
-
- if (scanner == null) {
- return false;
- }
-
- if (!canScan()) {
- return false;
- }
-
- // scanMode note: SCAN_FAILED_FEATURE_UNSUPPORTED is caused (at least on some devices) if
- // setReportDelay() is used or if SCAN_MODE_LOW_LATENCY isn't used.
- int scanMode = ScanSettings.SCAN_MODE_LOW_LATENCY;
-
- assert mScanCallback == null;
- mScanCallback = new ScanCallback();
-
- try {
- scanner.startScan(filters, scanMode, mScanCallback);
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "Cannot start scan: " + e);
- mScanCallback = null;
- return false;
- } catch (IllegalStateException e) {
- Log.e(TAG, "Adapter is off. Cannot start scan: " + e);
- mScanCallback = null;
- return false;
- }
- return true;
- }
-
- /**
- * Stops the Low Energy scan.
- * @return True if a scan was in progress.
- */
- @CalledByNative
- private boolean stopScan() {
- if (mScanCallback == null) {
- return false;
- }
-
- try {
- Wrappers.BluetoothLeScannerWrapper scanner = mAdapter.getBluetoothLeScanner();
- if (scanner != null) {
- scanner.stopScan(mScanCallback);
- }
- } catch (IllegalArgumentException e) {
- Log.e(TAG, "Cannot stop scan: " + e);
- } catch (IllegalStateException e) {
- Log.e(TAG, "Adapter is off. Cannot stop scan: " + e);
- }
- mScanCallback = null;
- return true;
- }
-
- // ---------------------------------------------------------------------------------------------
- // Implementation details:
-
- /**
- * @return true if Chromium has permission to scan for Bluetooth devices and location services
- * are on.
- */
- private boolean canScan() {
- LocationUtils locationUtils = LocationUtils.getInstance();
- return locationUtils.hasAndroidLocationPermission()
- && locationUtils.isSystemLocationSettingEnabled();
- }
-
- private void registerBroadcastReceiver() {
- if (mAdapter != null) {
- mAdapter.getContext().registerReceiver(
- this, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
- }
- }
-
- private void unregisterBroadcastReceiver() {
- if (mAdapter != null) {
- mAdapter.getContext().unregisterReceiver(this);
- }
- }
-
- /**
- * Implements callbacks used during a Low Energy scan by notifying upon
- * devices discovered or detecting a scan failure.
- */
- private class ScanCallback extends Wrappers.ScanCallbackWrapper {
- @Override
- public void onBatchScanResult(List<Wrappers.ScanResultWrapper> results) {
- Log.v(TAG, "onBatchScanResults");
- }
-
- @Override
- public void onScanResult(int callbackType, Wrappers.ScanResultWrapper result) {
- Log.v(TAG, "onScanResult %d %s %s", callbackType, result.getDevice().getAddress(),
- result.getDevice().getName());
-
- String[] uuid_strings;
- List<ParcelUuid> uuids = result.getScanRecord_getServiceUuids();
-
- if (uuids == null) {
- uuid_strings = new String[] {};
- } else {
- uuid_strings = new String[uuids.size()];
- for (int i = 0; i < uuids.size(); i++) {
- uuid_strings[i] = uuids.get(i).toString();
- }
- }
-
- String[] serviceDataKeys;
- byte[][] serviceDataValues;
- Map<ParcelUuid, byte[]> serviceData = result.getScanRecord_getServiceData();
- if (serviceData == null) {
- serviceDataKeys = new String[] {};
- serviceDataValues = new byte[][] {};
- } else {
- serviceDataKeys = new String[serviceData.size()];
- serviceDataValues = new byte[serviceData.size()][];
- int i = 0;
- for (Map.Entry<ParcelUuid, byte[]> serviceDataItem : serviceData.entrySet()) {
- serviceDataKeys[i] = serviceDataItem.getKey().toString();
- serviceDataValues[i++] = serviceDataItem.getValue();
- }
- }
-
- int[] manufacturerDataKeys;
- byte[][] manufacturerDataValues;
- SparseArray<byte[]> manufacturerData =
- result.getScanRecord_getManufacturerSpecificData();
- if (manufacturerData == null) {
- manufacturerDataKeys = new int[] {};
- manufacturerDataValues = new byte[][] {};
- } else {
- manufacturerDataKeys = new int[manufacturerData.size()];
- manufacturerDataValues = new byte[manufacturerData.size()][];
- for (int i = 0; i < manufacturerData.size(); i++) {
- manufacturerDataKeys[i] = manufacturerData.keyAt(i);
- manufacturerDataValues[i] = manufacturerData.valueAt(i);
- }
- }
-
- // Object can be destroyed, but Android keeps calling onScanResult.
- if (mNativeBluetoothAdapterAndroid != 0) {
- ChromeBluetoothAdapterJni.get().createOrUpdateDeviceOnScan(
- mNativeBluetoothAdapterAndroid, ChromeBluetoothAdapter.this,
- result.getDevice().getAddress(), result.getDevice(),
- result.getScanRecord_getDeviceName(), result.getRssi(), uuid_strings,
- result.getScanRecord_getTxPowerLevel(), serviceDataKeys, serviceDataValues,
- manufacturerDataKeys, manufacturerDataValues,
- result.getScanRecord_getAdvertiseFlags());
- }
- }
-
- @Override
- public void onScanFailed(int errorCode) {
- Log.w(TAG, "onScanFailed: %d", errorCode);
- ChromeBluetoothAdapterJni.get().onScanFailed(
- mNativeBluetoothAdapterAndroid, ChromeBluetoothAdapter.this);
- }
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
-
- if (isPresent() && BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
- int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
-
- Log.w(TAG, "onReceive: BluetoothAdapter.ACTION_STATE_CHANGED: %s",
- getBluetoothStateString(state));
-
- switch (state) {
- case BluetoothAdapter.STATE_ON:
- ChromeBluetoothAdapterJni.get().onAdapterStateChanged(
- mNativeBluetoothAdapterAndroid, ChromeBluetoothAdapter.this, true);
- break;
- case BluetoothAdapter.STATE_OFF:
- ChromeBluetoothAdapterJni.get().onAdapterStateChanged(
- mNativeBluetoothAdapterAndroid, ChromeBluetoothAdapter.this, false);
- break;
- default:
- // do nothing
- }
- }
- }
-
- private String getBluetoothStateString(int state) {
- switch (state) {
- case BluetoothAdapter.STATE_OFF:
- return "STATE_OFF";
- case BluetoothAdapter.STATE_ON:
- return "STATE_ON";
- case BluetoothAdapter.STATE_TURNING_OFF:
- return "STATE_TURNING_OFF";
- case BluetoothAdapter.STATE_TURNING_ON:
- return "STATE_TURNING_ON";
- default:
- assert false;
- return "illegal state: " + state;
- }
- }
-
- @NativeMethods
- interface Natives {
- // Binds to BluetoothAdapterAndroid::OnScanFailed.
- void onScanFailed(long nativeBluetoothAdapterAndroid, ChromeBluetoothAdapter caller);
-
- // Binds to BluetoothAdapterAndroid::CreateOrUpdateDeviceOnScan.
- void createOrUpdateDeviceOnScan(long nativeBluetoothAdapterAndroid,
- ChromeBluetoothAdapter caller, String address,
- Wrappers.BluetoothDeviceWrapper deviceWrapper, String localName, int rssi,
- String[] advertisedUuids, int txPower, String[] serviceDataKeys,
- Object[] serviceDataValues, int[] manufacturerDataKeys,
- Object[] manufacturerDataValues, int advertiseFlags);
-
- // Binds to BluetoothAdapterAndroid::nativeOnAdapterStateChanged
- void onAdapterStateChanged(
- long nativeBluetoothAdapterAndroid, ChromeBluetoothAdapter caller, boolean powered);
- }
-}
diff --git a/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothDevice.java b/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothDevice.java
deleted file mode 100644
index e134b66cf13..00000000000
--- a/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothDevice.java
+++ /dev/null
@@ -1,326 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.device.bluetooth;
-
-import android.annotation.TargetApi;
-import android.bluetooth.BluetoothDevice;
-import android.os.Build;
-
-import org.chromium.base.ContextUtils;
-import org.chromium.base.Log;
-import org.chromium.base.annotations.CalledByNative;
-import org.chromium.base.annotations.JNIAdditionalImport;
-import org.chromium.base.annotations.JNINamespace;
-import org.chromium.base.annotations.NativeMethods;
-import org.chromium.base.metrics.RecordHistogram;
-
-import java.util.HashMap;
-
-/**
- * Exposes android.bluetooth.BluetoothDevice as necessary for C++
- * device::BluetoothDeviceAndroid.
- *
- * Lifetime is controlled by device::BluetoothDeviceAndroid.
- */
-@JNINamespace("device")
-@JNIAdditionalImport(Wrappers.class)
-@TargetApi(Build.VERSION_CODES.M)
-final class ChromeBluetoothDevice {
- private static final String TAG = "Bluetooth";
-
- private long mNativeBluetoothDeviceAndroid;
- final Wrappers.BluetoothDeviceWrapper mDevice;
- Wrappers.BluetoothGattWrapper mBluetoothGatt;
- private final BluetoothGattCallbackImpl mBluetoothGattCallbackImpl;
- final HashMap<Wrappers.BluetoothGattCharacteristicWrapper,
- ChromeBluetoothRemoteGattCharacteristic> mWrapperToChromeCharacteristicsMap;
- final HashMap<Wrappers.BluetoothGattDescriptorWrapper, ChromeBluetoothRemoteGattDescriptor>
- mWrapperToChromeDescriptorsMap;
-
- private ChromeBluetoothDevice(
- long nativeBluetoothDeviceAndroid, Wrappers.BluetoothDeviceWrapper deviceWrapper) {
- mNativeBluetoothDeviceAndroid = nativeBluetoothDeviceAndroid;
- mDevice = deviceWrapper;
- mBluetoothGattCallbackImpl = new BluetoothGattCallbackImpl();
- mWrapperToChromeCharacteristicsMap =
- new HashMap<Wrappers.BluetoothGattCharacteristicWrapper,
- ChromeBluetoothRemoteGattCharacteristic>();
- mWrapperToChromeDescriptorsMap = new HashMap<Wrappers.BluetoothGattDescriptorWrapper,
- ChromeBluetoothRemoteGattDescriptor>();
- Log.v(TAG, "ChromeBluetoothDevice created.");
- }
-
- /**
- * Handles C++ object being destroyed.
- */
- @CalledByNative
- private void onBluetoothDeviceAndroidDestruction() {
- if (mBluetoothGatt != null) {
- mBluetoothGatt.close();
- mBluetoothGatt = null;
- }
- mNativeBluetoothDeviceAndroid = 0;
- }
-
- // ---------------------------------------------------------------------------------------------
- // BluetoothDeviceAndroid methods implemented in java:
-
- // Implements BluetoothDeviceAndroid::Create.
- @CalledByNative
- private static ChromeBluetoothDevice create(
- long nativeBluetoothDeviceAndroid, Wrappers.BluetoothDeviceWrapper deviceWrapper) {
- return new ChromeBluetoothDevice(nativeBluetoothDeviceAndroid, deviceWrapper);
- }
-
- // Implements BluetoothDeviceAndroid::GetBluetoothClass.
- @CalledByNative
- private int getBluetoothClass() {
- return mDevice.getBluetoothClass_getDeviceClass();
- }
-
- // Implements BluetoothDeviceAndroid::GetAddress.
- @CalledByNative
- private String getAddress() {
- return mDevice.getAddress();
- }
-
- // Implements BluetoothDeviceAndroid::GetName.
- @CalledByNative
- private String getName() {
- return mDevice.getName();
- }
-
- // Implements BluetoothDeviceAndroid::IsPaired.
- @CalledByNative
- private boolean isPaired() {
- return mDevice.getBondState() == BluetoothDevice.BOND_BONDED;
- }
-
- // Implements BluetoothDeviceAndroid::CreateGattConnectionImpl.
- @CalledByNative
- private void createGattConnectionImpl() {
- Log.i(TAG, "connectGatt");
-
- if (mBluetoothGatt != null) mBluetoothGatt.close();
-
- // autoConnect set to false as under experimentation using autoConnect failed to complete
- // connections.
- mBluetoothGatt = mDevice.connectGatt(ContextUtils.getApplicationContext(),
- false /* autoConnect */, mBluetoothGattCallbackImpl,
- // Prefer LE for dual-mode devices due to lower energy consumption.
- BluetoothDevice.TRANSPORT_LE);
- }
-
- // Implements BluetoothDeviceAndroid::DisconnectGatt.
- @CalledByNative
- private void disconnectGatt() {
- Log.i(TAG, "BluetoothGatt.disconnect");
- if (mBluetoothGatt != null) mBluetoothGatt.disconnect();
- }
-
- // Implements callbacks related to a GATT connection.
- private class BluetoothGattCallbackImpl extends Wrappers.BluetoothGattCallbackWrapper {
- @Override
- public void onConnectionStateChange(final int status, final int newState) {
- Log.i(TAG, "onConnectionStateChange status:%d newState:%s", status,
- (newState == android.bluetooth.BluetoothProfile.STATE_CONNECTED)
- ? "Connected"
- : "Disconnected");
- Wrappers.ThreadUtilsWrapper.getInstance().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (newState == android.bluetooth.BluetoothProfile.STATE_CONNECTED) {
- RecordHistogram.recordSparseHistogram(
- "Bluetooth.Web.Android.onConnectionStateChange.Status.Connected",
- status);
- mBluetoothGatt.discoverServices();
- } else if (newState == android.bluetooth.BluetoothProfile.STATE_DISCONNECTED) {
- RecordHistogram.recordSparseHistogram(
- "Bluetooth.Web.Android.onConnectionStateChange.Status.Disconnected",
- status);
- if (mBluetoothGatt != null) {
- mBluetoothGatt.close();
- mBluetoothGatt = null;
- }
- } else {
- RecordHistogram.recordSparseHistogram(
- "Bluetooth.Web.Android.onConnectionStateChange.Status.InvalidState",
- status);
- }
- if (mNativeBluetoothDeviceAndroid != 0) {
- ChromeBluetoothDeviceJni.get().onConnectionStateChange(
- mNativeBluetoothDeviceAndroid, ChromeBluetoothDevice.this, status,
- newState == android.bluetooth.BluetoothProfile.STATE_CONNECTED);
- }
- }
- });
- }
-
- @Override
- public void onServicesDiscovered(final int status) {
- Log.i(TAG, "onServicesDiscovered status:%d==%s", status,
- status == android.bluetooth.BluetoothGatt.GATT_SUCCESS ? "OK" : "Error");
- Wrappers.ThreadUtilsWrapper.getInstance().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (mNativeBluetoothDeviceAndroid != 0) {
- // When the device disconnects it deletes
- // mBluetoothGatt, so we need to check it's not null.
- if (mBluetoothGatt == null) {
- RecordHistogram.recordSparseHistogram(
- "Bluetooth.Web.Android.onServicesDiscovered.Status."
- + "Disconnected",
- status);
- return;
- }
- RecordHistogram.recordSparseHistogram(
- "Bluetooth.Web.Android.onServicesDiscovered.Status.Connected",
- status);
-
- // TODO(crbug.com/576906): Update or replace existing GATT objects if they
- // change after initial discovery.
- for (Wrappers.BluetoothGattServiceWrapper service :
- mBluetoothGatt.getServices()) {
- // Create an adapter unique service ID. getInstanceId only differs
- // between service instances with the same UUID on this device.
- String serviceInstanceId = getAddress() + "/"
- + service.getUuid().toString() + "," + service.getInstanceId();
- ChromeBluetoothDeviceJni.get().createGattRemoteService(
- mNativeBluetoothDeviceAndroid, ChromeBluetoothDevice.this,
- serviceInstanceId, service);
- }
- ChromeBluetoothDeviceJni.get().onGattServicesDiscovered(
- mNativeBluetoothDeviceAndroid, ChromeBluetoothDevice.this);
- }
- }
- });
- }
-
- @Override
- public void onCharacteristicChanged(
- final Wrappers.BluetoothGattCharacteristicWrapper characteristic) {
- Log.i(TAG, "device onCharacteristicChanged.");
- // Copy the characteristic's value for this event so that new notifications that
- // arrive before the posted task runs do not affect this event's value.
- final byte[] value = characteristic.getValue();
- Wrappers.ThreadUtilsWrapper.getInstance().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- ChromeBluetoothRemoteGattCharacteristic chromeCharacteristic =
- mWrapperToChromeCharacteristicsMap.get(characteristic);
- if (chromeCharacteristic == null) {
- // Android events arriving with no Chrome object is expected rarely only
- // when the event races object destruction.
- Log.v(TAG, "onCharacteristicChanged when chromeCharacteristic == null.");
- } else {
- chromeCharacteristic.onCharacteristicChanged(value);
- }
- }
- });
- }
-
- @Override
- public void onCharacteristicRead(
- final Wrappers.BluetoothGattCharacteristicWrapper characteristic,
- final int status) {
- Wrappers.ThreadUtilsWrapper.getInstance().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- ChromeBluetoothRemoteGattCharacteristic chromeCharacteristic =
- mWrapperToChromeCharacteristicsMap.get(characteristic);
- if (chromeCharacteristic == null) {
- // Android events arriving with no Chrome object is expected rarely: only
- // when the event races object destruction.
- Log.v(TAG, "onCharacteristicRead when chromeCharacteristic == null.");
- } else {
- RecordHistogram.recordSparseHistogram(
- "Bluetooth.Web.Android.onCharacteristicRead.Status", status);
- chromeCharacteristic.onCharacteristicRead(status);
- }
- }
- });
- }
-
- @Override
- public void onCharacteristicWrite(
- final Wrappers.BluetoothGattCharacteristicWrapper characteristic,
- final int status) {
- Wrappers.ThreadUtilsWrapper.getInstance().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- ChromeBluetoothRemoteGattCharacteristic chromeCharacteristic =
- mWrapperToChromeCharacteristicsMap.get(characteristic);
- if (chromeCharacteristic == null) {
- // Android events arriving with no Chrome object is expected rarely: only
- // when the event races object destruction.
- Log.v(TAG, "onCharacteristicWrite when chromeCharacteristic == null.");
- } else {
- RecordHistogram.recordSparseHistogram(
- "Bluetooth.Web.Android.onCharacteristicWrite.Status", status);
- chromeCharacteristic.onCharacteristicWrite(status);
- }
- }
- });
- }
-
- @Override
- public void onDescriptorRead(
- final Wrappers.BluetoothGattDescriptorWrapper descriptor, final int status) {
- Wrappers.ThreadUtilsWrapper.getInstance().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- ChromeBluetoothRemoteGattDescriptor chromeDescriptor =
- mWrapperToChromeDescriptorsMap.get(descriptor);
- if (chromeDescriptor == null) {
- // Android events arriving with no Chrome object is expected rarely: only
- // when the event races object destruction.
- Log.v(TAG, "onDescriptorRead when chromeDescriptor == null.");
- } else {
- RecordHistogram.recordSparseHistogram(
- "Bluetooth.Web.Android.onDescriptorRead.Status", status);
- chromeDescriptor.onDescriptorRead(status);
- }
- }
- });
- }
-
- @Override
- public void onDescriptorWrite(
- final Wrappers.BluetoothGattDescriptorWrapper descriptor, final int status) {
- Wrappers.ThreadUtilsWrapper.getInstance().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- ChromeBluetoothRemoteGattDescriptor chromeDescriptor =
- mWrapperToChromeDescriptorsMap.get(descriptor);
- if (chromeDescriptor == null) {
- // Android events arriving with no Chrome object is expected rarely: only
- // when the event races object destruction.
- Log.v(TAG, "onDescriptorWrite when chromeDescriptor == null.");
- } else {
- RecordHistogram.recordSparseHistogram(
- "Bluetooth.Web.Android.onDescriptorWrite.Status", status);
- chromeDescriptor.onDescriptorWrite(status);
- }
- }
- });
- }
- }
-
- @NativeMethods
- interface Natives {
- // Binds to BluetoothDeviceAndroid::OnConnectionStateChange.
- void onConnectionStateChange(long nativeBluetoothDeviceAndroid,
- ChromeBluetoothDevice caller, int status, boolean connected);
-
- // Binds to BluetoothDeviceAndroid::CreateGattRemoteService.
- void createGattRemoteService(long nativeBluetoothDeviceAndroid,
- ChromeBluetoothDevice caller, String instanceId,
- Wrappers.BluetoothGattServiceWrapper serviceWrapper);
-
- // Binds to BluetoothDeviceAndroid::GattServicesDiscovered.
- void onGattServicesDiscovered(
- long nativeBluetoothDeviceAndroid, ChromeBluetoothDevice caller);
- }
-}
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
deleted file mode 100644
index 39156474261..00000000000
--- a/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothRemoteGattCharacteristic.java
+++ /dev/null
@@ -1,196 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.device.bluetooth;
-
-import android.annotation.TargetApi;
-import android.os.Build;
-
-import org.chromium.base.Log;
-import org.chromium.base.annotations.CalledByNative;
-import org.chromium.base.annotations.JNIAdditionalImport;
-import org.chromium.base.annotations.JNINamespace;
-import org.chromium.base.annotations.NativeMethods;
-
-import java.util.List;
-
-/**
- * Exposes android.bluetooth.BluetoothGattCharacteristic as necessary
- * for C++ device::BluetoothRemoteGattCharacteristicAndroid.
- *
- * Lifetime is controlled by
- * device::BluetoothRemoteGattCharacteristicAndroid.
- */
-@JNINamespace("device")
-@JNIAdditionalImport(Wrappers.class)
-@TargetApi(Build.VERSION_CODES.M)
-final class ChromeBluetoothRemoteGattCharacteristic {
- private static final String TAG = "Bluetooth";
-
- private long mNativeBluetoothRemoteGattCharacteristicAndroid;
- final Wrappers.BluetoothGattCharacteristicWrapper mCharacteristic;
- final String mInstanceId;
- final ChromeBluetoothDevice mChromeDevice;
-
- private ChromeBluetoothRemoteGattCharacteristic(
- long nativeBluetoothRemoteGattCharacteristicAndroid,
- Wrappers.BluetoothGattCharacteristicWrapper characteristicWrapper, String instanceId,
- ChromeBluetoothDevice chromeDevice) {
- mNativeBluetoothRemoteGattCharacteristicAndroid =
- nativeBluetoothRemoteGattCharacteristicAndroid;
- mCharacteristic = characteristicWrapper;
- mInstanceId = instanceId;
- mChromeDevice = chromeDevice;
-
- mChromeDevice.mWrapperToChromeCharacteristicsMap.put(characteristicWrapper, this);
-
- Log.v(TAG, "ChromeBluetoothRemoteGattCharacteristic created.");
- }
-
- /**
- * Handles C++ object being destroyed.
- */
- @CalledByNative
- private void onBluetoothRemoteGattCharacteristicAndroidDestruction() {
- Log.v(TAG, "ChromeBluetoothRemoteGattCharacteristic Destroyed.");
- if (mChromeDevice.mBluetoothGatt != null) {
- mChromeDevice.mBluetoothGatt.setCharacteristicNotification(mCharacteristic, false);
- }
- mNativeBluetoothRemoteGattCharacteristicAndroid = 0;
- mChromeDevice.mWrapperToChromeCharacteristicsMap.remove(mCharacteristic);
- }
-
- void onCharacteristicChanged(byte[] value) {
- Log.i(TAG, "onCharacteristicChanged");
- if (mNativeBluetoothRemoteGattCharacteristicAndroid != 0) {
- ChromeBluetoothRemoteGattCharacteristicJni.get().onChanged(
- mNativeBluetoothRemoteGattCharacteristicAndroid,
- ChromeBluetoothRemoteGattCharacteristic.this, value);
- }
- }
-
- void onCharacteristicRead(int status) {
- Log.i(TAG, "onCharacteristicRead status:%d==%s", status,
- status == android.bluetooth.BluetoothGatt.GATT_SUCCESS ? "OK" : "Error");
- if (mNativeBluetoothRemoteGattCharacteristicAndroid != 0) {
- ChromeBluetoothRemoteGattCharacteristicJni.get().onRead(
- mNativeBluetoothRemoteGattCharacteristicAndroid,
- ChromeBluetoothRemoteGattCharacteristic.this, status,
- mCharacteristic.getValue());
- }
- }
-
- void onCharacteristicWrite(int status) {
- Log.i(TAG, "onCharacteristicWrite status:%d==%s", status,
- status == android.bluetooth.BluetoothGatt.GATT_SUCCESS ? "OK" : "Error");
- if (mNativeBluetoothRemoteGattCharacteristicAndroid != 0) {
- ChromeBluetoothRemoteGattCharacteristicJni.get().onWrite(
- mNativeBluetoothRemoteGattCharacteristicAndroid,
- ChromeBluetoothRemoteGattCharacteristic.this, status);
- }
- }
-
- // ---------------------------------------------------------------------------------------------
- // BluetoothRemoteGattCharacteristicAndroid methods implemented in java:
-
- // Implements BluetoothRemoteGattCharacteristicAndroid::Create.
- @CalledByNative
- private static ChromeBluetoothRemoteGattCharacteristic create(
- long nativeBluetoothRemoteGattCharacteristicAndroid,
- Wrappers.BluetoothGattCharacteristicWrapper characteristicWrapper, String instanceId,
- ChromeBluetoothDevice chromeDevice) {
- return new ChromeBluetoothRemoteGattCharacteristic(
- nativeBluetoothRemoteGattCharacteristicAndroid, characteristicWrapper, instanceId,
- chromeDevice);
- }
-
- // Implements BluetoothRemoteGattCharacteristicAndroid::GetUUID.
- @CalledByNative
- private String getUUID() {
- return mCharacteristic.getUuid().toString();
- }
-
- // Implements BluetoothRemoteGattCharacteristicAndroid::GetProperties.
- @CalledByNative
- private int getProperties() {
- // TODO(scheib): Must read Extended Properties Descriptor. crbug.com/548449
- return mCharacteristic.getProperties();
- }
-
- // Implements BluetoothRemoteGattCharacteristicAndroid::ReadRemoteCharacteristic.
- @CalledByNative
- private boolean readRemoteCharacteristic() {
- if (!mChromeDevice.mBluetoothGatt.readCharacteristic(mCharacteristic)) {
- Log.i(TAG, "readRemoteCharacteristic readCharacteristic failed.");
- return false;
- }
- return true;
- }
-
- // Implements BluetoothRemoteGattCharacteristicAndroid::WriteRemoteCharacteristic.
- @CalledByNative
- 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;
- }
- return true;
- }
-
- // Enable or disable the notifications for this characteristic.
- @CalledByNative
- private boolean setCharacteristicNotification(boolean enabled) {
- return mChromeDevice.mBluetoothGatt.setCharacteristicNotification(mCharacteristic, enabled);
- }
-
- // Creates objects for all descriptors. Designed only to be called by
- // BluetoothRemoteGattCharacteristicAndroid::EnsureDescriptorsCreated.
- @CalledByNative
- private void createDescriptors() {
- List<Wrappers.BluetoothGattDescriptorWrapper> descriptors =
- mCharacteristic.getDescriptors();
- // descriptorInstanceId ensures duplicate UUIDs have unique instance
- // IDs. BluetoothGattDescriptor does not offer getInstanceId the way
- // BluetoothGattCharacteristic does.
- //
- // TODO(crbug.com/576906) Do not reuse IDs upon onServicesDiscovered.
- int instanceIdCounter = 0;
- for (Wrappers.BluetoothGattDescriptorWrapper descriptor : descriptors) {
- String descriptorInstanceId =
- mInstanceId + "/" + descriptor.getUuid().toString() + ";" + instanceIdCounter++;
- ChromeBluetoothRemoteGattCharacteristicJni.get().createGattRemoteDescriptor(
- mNativeBluetoothRemoteGattCharacteristicAndroid,
- ChromeBluetoothRemoteGattCharacteristic.this, descriptorInstanceId, descriptor,
- mChromeDevice);
- }
- }
-
- @NativeMethods
- interface Natives {
- // Binds to BluetoothRemoteGattCharacteristicAndroid::OnChanged.
- void onChanged(long nativeBluetoothRemoteGattCharacteristicAndroid,
- ChromeBluetoothRemoteGattCharacteristic caller, byte[] value);
-
- // Binds to BluetoothRemoteGattCharacteristicAndroid::OnRead.
- void onRead(long nativeBluetoothRemoteGattCharacteristicAndroid,
- ChromeBluetoothRemoteGattCharacteristic caller, int status, byte[] value);
-
- // Binds to BluetoothRemoteGattCharacteristicAndroid::OnWrite.
- void onWrite(long nativeBluetoothRemoteGattCharacteristicAndroid,
- ChromeBluetoothRemoteGattCharacteristic caller, int status);
-
- // Binds to BluetoothRemoteGattCharacteristicAndroid::CreateGattRemoteDescriptor.
- void createGattRemoteDescriptor(long nativeBluetoothRemoteGattCharacteristicAndroid,
- ChromeBluetoothRemoteGattCharacteristic caller, String instanceId,
- Wrappers.BluetoothGattDescriptorWrapper descriptorWrapper,
- ChromeBluetoothDevice chromeBluetoothDevice);
- }
-}
diff --git a/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothRemoteGattDescriptor.java b/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothRemoteGattDescriptor.java
deleted file mode 100644
index 4214c4281be..00000000000
--- a/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothRemoteGattDescriptor.java
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.device.bluetooth;
-
-import org.chromium.base.Log;
-import org.chromium.base.annotations.CalledByNative;
-import org.chromium.base.annotations.JNIAdditionalImport;
-import org.chromium.base.annotations.JNINamespace;
-import org.chromium.base.annotations.NativeMethods;
-
-/**
- * Exposes android.bluetooth.BluetoothGattDescriptor as necessary
- * for C++ device::BluetoothRemoteGattDescriptorAndroid.
- *
- * Lifetime is controlled by device::BluetoothRemoteGattDescriptorAndroid.
- */
-@JNINamespace("device")
-@JNIAdditionalImport(Wrappers.class)
-final class ChromeBluetoothRemoteGattDescriptor {
- private static final String TAG = "Bluetooth";
-
- private long mNativeBluetoothRemoteGattDescriptorAndroid;
- final Wrappers.BluetoothGattDescriptorWrapper mDescriptor;
- final ChromeBluetoothDevice mChromeDevice;
-
- private ChromeBluetoothRemoteGattDescriptor(long nativeBluetoothRemoteGattDescriptorAndroid,
- Wrappers.BluetoothGattDescriptorWrapper descriptorWrapper,
- ChromeBluetoothDevice chromeDevice) {
- mNativeBluetoothRemoteGattDescriptorAndroid = nativeBluetoothRemoteGattDescriptorAndroid;
- mDescriptor = descriptorWrapper;
- mChromeDevice = chromeDevice;
-
- mChromeDevice.mWrapperToChromeDescriptorsMap.put(descriptorWrapper, this);
-
- Log.v(TAG, "ChromeBluetoothRemoteGattDescriptor created.");
- }
-
- /**
- * Handles C++ object being destroyed.
- */
- @CalledByNative
- private void onBluetoothRemoteGattDescriptorAndroidDestruction() {
- Log.v(TAG, "ChromeBluetoothRemoteGattDescriptor Destroyed.");
- mNativeBluetoothRemoteGattDescriptorAndroid = 0;
- mChromeDevice.mWrapperToChromeDescriptorsMap.remove(mDescriptor);
- }
-
- void onDescriptorRead(int status) {
- Log.i(TAG, "onDescriptorRead status:%d==%s", status,
- status == android.bluetooth.BluetoothGatt.GATT_SUCCESS ? "OK" : "Error");
- if (mNativeBluetoothRemoteGattDescriptorAndroid != 0) {
- ChromeBluetoothRemoteGattDescriptorJni.get().onRead(
- mNativeBluetoothRemoteGattDescriptorAndroid,
- ChromeBluetoothRemoteGattDescriptor.this, status, mDescriptor.getValue());
- }
- }
-
- void onDescriptorWrite(int status) {
- Log.i(TAG, "onDescriptorWrite status:%d==%s", status,
- status == android.bluetooth.BluetoothGatt.GATT_SUCCESS ? "OK" : "Error");
- if (mNativeBluetoothRemoteGattDescriptorAndroid != 0) {
- ChromeBluetoothRemoteGattDescriptorJni.get().onWrite(
- mNativeBluetoothRemoteGattDescriptorAndroid,
- ChromeBluetoothRemoteGattDescriptor.this, status);
- }
- }
-
- // ---------------------------------------------------------------------------------------------
- // BluetoothRemoteGattDescriptorAndroid methods implemented in java:
-
- // Implements BluetoothRemoteGattDescriptorAndroid::Create.
- @CalledByNative
- private static ChromeBluetoothRemoteGattDescriptor create(
- long nativeBluetoothRemoteGattDescriptorAndroid,
- Wrappers.BluetoothGattDescriptorWrapper descriptorWrapper,
- ChromeBluetoothDevice chromeDevice) {
- return new ChromeBluetoothRemoteGattDescriptor(
- nativeBluetoothRemoteGattDescriptorAndroid, descriptorWrapper, chromeDevice);
- }
-
- // Implements BluetoothRemoteGattDescriptorAndroid::GetUUID.
- @CalledByNative
- private String getUUID() {
- return mDescriptor.getUuid().toString();
- }
-
- // Implements BluetoothRemoteGattDescriptorAndroid::ReadRemoteDescriptor.
- @CalledByNative
- private boolean readRemoteDescriptor() {
- if (!mChromeDevice.mBluetoothGatt.readDescriptor(mDescriptor)) {
- Log.i(TAG, "readRemoteDescriptor readDescriptor failed.");
- return false;
- }
- return true;
- }
-
- // Implements BluetoothRemoteGattDescriptorAndroid::WriteRemoteDescriptor.
- @CalledByNative
- private boolean writeRemoteDescriptor(byte[] value) {
- if (!mDescriptor.setValue(value)) {
- Log.i(TAG, "writeRemoteDescriptor setValue failed.");
- return false;
- }
- if (!mChromeDevice.mBluetoothGatt.writeDescriptor(mDescriptor)) {
- Log.i(TAG, "writeRemoteDescriptor writeDescriptor failed.");
- return false;
- }
- return true;
- }
-
- @NativeMethods
- interface Natives {
- // Binds to BluetoothRemoteGattDescriptorAndroid::OnRead.
- void onRead(long nativeBluetoothRemoteGattDescriptorAndroid,
- ChromeBluetoothRemoteGattDescriptor caller, int status, byte[] value);
-
- // Binds to BluetoothRemoteGattDescriptorAndroid::OnWrite.
- void onWrite(long nativeBluetoothRemoteGattDescriptorAndroid,
- ChromeBluetoothRemoteGattDescriptor caller, int status);
- }
-}
diff --git a/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothRemoteGattService.java b/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothRemoteGattService.java
deleted file mode 100644
index fd6803e1ece..00000000000
--- a/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothRemoteGattService.java
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.device.bluetooth;
-
-import org.chromium.base.Log;
-import org.chromium.base.annotations.CalledByNative;
-import org.chromium.base.annotations.JNIAdditionalImport;
-import org.chromium.base.annotations.JNINamespace;
-import org.chromium.base.annotations.NativeMethods;
-
-import java.util.List;
-
-/**
- * Exposes android.bluetooth.BluetoothGattService as necessary
- * for C++ device::BluetoothRemoteGattServiceAndroid.
- *
- * Lifetime is controlled by
- * device::BluetoothRemoteGattServiceAndroid.
- */
-@JNINamespace("device")
-@JNIAdditionalImport(Wrappers.class)
-final class ChromeBluetoothRemoteGattService {
- private static final String TAG = "Bluetooth";
-
- private long mNativeBluetoothRemoteGattServiceAndroid;
- final Wrappers.BluetoothGattServiceWrapper mService;
- final String mInstanceId;
- ChromeBluetoothDevice mChromeDevice;
-
- private ChromeBluetoothRemoteGattService(long nativeBluetoothRemoteGattServiceAndroid,
- Wrappers.BluetoothGattServiceWrapper serviceWrapper, String instanceId,
- ChromeBluetoothDevice chromeDevice) {
- mNativeBluetoothRemoteGattServiceAndroid = nativeBluetoothRemoteGattServiceAndroid;
- mService = serviceWrapper;
- mInstanceId = instanceId;
- mChromeDevice = chromeDevice;
- Log.v(TAG, "ChromeBluetoothRemoteGattService created.");
- }
-
- /**
- * Handles C++ object being destroyed.
- */
- @CalledByNative
- private void onBluetoothRemoteGattServiceAndroidDestruction() {
- mNativeBluetoothRemoteGattServiceAndroid = 0;
- }
-
- // Implements BluetoothRemoteGattServiceAndroid::Create.
- @CalledByNative
- private static ChromeBluetoothRemoteGattService create(
- long nativeBluetoothRemoteGattServiceAndroid,
- Wrappers.BluetoothGattServiceWrapper serviceWrapper, String instanceId,
- ChromeBluetoothDevice chromeDevice) {
- return new ChromeBluetoothRemoteGattService(
- nativeBluetoothRemoteGattServiceAndroid, serviceWrapper, instanceId, chromeDevice);
- }
-
- // Implements BluetoothRemoteGattServiceAndroid::GetUUID.
- @CalledByNative
- private String getUUID() {
- return mService.getUuid().toString();
- }
-
- // Creates objects for all characteristics. Designed only to be called by
- // BluetoothRemoteGattServiceAndroid::EnsureCharacteristicsCreated.
- @CalledByNative
- private void createCharacteristics() {
- List<Wrappers.BluetoothGattCharacteristicWrapper> characteristics =
- mService.getCharacteristics();
- for (Wrappers.BluetoothGattCharacteristicWrapper characteristic : characteristics) {
- // Create an adapter unique characteristic ID. getInstanceId only differs between
- // characteristic instances with the same UUID on this service.
- String characteristicInstanceId = mInstanceId + "/"
- + characteristic.getUuid().toString() + "," + characteristic.getInstanceId();
- ChromeBluetoothRemoteGattServiceJni.get().createGattRemoteCharacteristic(
- mNativeBluetoothRemoteGattServiceAndroid, ChromeBluetoothRemoteGattService.this,
- characteristicInstanceId, characteristic, mChromeDevice);
- }
- }
-
- @NativeMethods
- interface Natives {
- // Binds to BluetoothRemoteGattServiceAndroid::CreateGattRemoteCharacteristic.
- void createGattRemoteCharacteristic(long nativeBluetoothRemoteGattServiceAndroid,
- ChromeBluetoothRemoteGattService caller, String instanceId,
- Wrappers.BluetoothGattCharacteristicWrapper characteristicWrapper,
- ChromeBluetoothDevice chromeBluetoothDevice);
- }
-}
diff --git a/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothScanFilterBuilder.java b/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothScanFilterBuilder.java
deleted file mode 100644
index e9e254670aa..00000000000
--- a/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothScanFilterBuilder.java
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2019 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.
-
-package org.chromium.device.bluetooth;
-
-import android.annotation.TargetApi;
-import android.bluetooth.le.ScanFilter;
-import android.os.Build;
-import android.os.ParcelUuid;
-
-import org.chromium.base.annotations.CalledByNative;
-import org.chromium.base.annotations.JNIAdditionalImport;
-import org.chromium.base.annotations.JNINamespace;
-
-/**
- * Exposes android.bluetooth.le.ScanFilter.Builder as necessary for C++.
- * This class is used to implement
- * BluetoothAdapterAndroid::CreateAndroidFilter()
- */
-@JNINamespace("device")
-@JNIAdditionalImport(Wrappers.class)
-@TargetApi(Build.VERSION_CODES.M)
-final class ChromeBluetoothScanFilterBuilder {
- private ScanFilter.Builder mBuilder;
-
- /**
- * Constructs a ChromeBluetoothScanFilter
- */
- public ChromeBluetoothScanFilterBuilder() {
- mBuilder = new ScanFilter.Builder();
- }
-
- // Creates and returns a new ChromeBluetoothScanFilterBuilder
- @CalledByNative
- private static ChromeBluetoothScanFilterBuilder create() {
- return new ChromeBluetoothScanFilterBuilder();
- }
-
- @CalledByNative
- private void setServiceUuid(String uuid) {
- if (uuid != null) {
- mBuilder.setServiceUuid(ParcelUuid.fromString(uuid));
- }
- }
-
- @CalledByNative
- private void setDeviceName(String deviceName) {
- if (deviceName != null) {
- mBuilder.setDeviceName(deviceName);
- }
- }
-
- @CalledByNative
- public ScanFilter build() {
- return mBuilder.build();
- }
-}
diff --git a/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothScanFilterList.java b/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothScanFilterList.java
deleted file mode 100644
index 79d290cb495..00000000000
--- a/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothScanFilterList.java
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2019 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.
-
-package org.chromium.device.bluetooth;
-
-import android.annotation.TargetApi;
-import android.bluetooth.le.ScanFilter;
-import android.os.Build;
-
-import org.chromium.base.annotations.CalledByNative;
-import org.chromium.base.annotations.JNIAdditionalImport;
-import org.chromium.base.annotations.JNINamespace;
-
-import java.util.ArrayList;
-
-/**
- * Allows for the creation of a Java ArrayList of the ScanFilter object.
- */
-@JNINamespace("device")
-@JNIAdditionalImport(Wrappers.class)
-@TargetApi(Build.VERSION_CODES.M)
-final class ChromeBluetoothScanFilterList {
- ArrayList<ScanFilter> mFilters;
-
- /**
- * Constructs a ChromeBluetoothScanFilterList
- */
- public ChromeBluetoothScanFilterList() {
- mFilters = new ArrayList<>();
- }
-
- @CalledByNative
- private static ChromeBluetoothScanFilterList create() {
- return new ChromeBluetoothScanFilterList();
- }
-
- @CalledByNative
- private void addFilter(ScanFilter filter) {
- mFilters.add(filter);
- }
-
- @CalledByNative
- public ArrayList<ScanFilter> getList() {
- return mFilters;
- }
-}
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
deleted file mode 100644
index 3829a8ff345..00000000000
--- a/chromium/device/bluetooth/android/java/src/org/chromium/device/bluetooth/Wrappers.java
+++ /dev/null
@@ -1,643 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.device.bluetooth;
-
-import android.Manifest;
-import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothGatt;
-import android.bluetooth.BluetoothGattCallback;
-import android.bluetooth.BluetoothGattCharacteristic;
-import android.bluetooth.BluetoothGattDescriptor;
-import android.bluetooth.BluetoothGattService;
-import android.bluetooth.le.BluetoothLeScanner;
-import android.bluetooth.le.ScanCallback;
-import android.bluetooth.le.ScanFilter;
-import android.bluetooth.le.ScanResult;
-import android.bluetooth.le.ScanSettings;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.os.ParcelUuid;
-import android.util.SparseArray;
-
-import org.chromium.base.ContextUtils;
-import org.chromium.base.Log;
-import org.chromium.base.ThreadUtils;
-import org.chromium.base.annotations.CalledByNative;
-import org.chromium.base.annotations.JNINamespace;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-/**
- * Wrapper classes around android.bluetooth.* classes that provide an
- * indirection layer enabling fake implementations when running tests.
- *
- * Each Wrapper base class accepts an Android API object and passes through
- * calls to it. When under test, Fake subclasses override all methods that
- * pass through to the Android object and instead provide fake implementations.
- */
-@JNINamespace("device")
-@TargetApi(Build.VERSION_CODES.M)
-class Wrappers {
- private static final String TAG = "Bluetooth";
-
- public static final int DEVICE_CLASS_UNSPECIFIED = 0x1F00;
-
- /**
- * Wraps base.ThreadUtils.
- * base.ThreadUtils has a set of static method to interact with the
- * UI Thread. To be able to provide a set of test methods, ThreadUtilsWrapper
- * uses the factory pattern.
- */
- static class ThreadUtilsWrapper {
- private static Factory sFactory;
-
- private static ThreadUtilsWrapper sInstance;
-
- protected ThreadUtilsWrapper() {}
-
- /**
- * Returns the singleton instance of ThreadUtilsWrapper, creating it if needed.
- */
- public static ThreadUtilsWrapper getInstance() {
- if (sInstance == null) {
- if (sFactory == null) {
- sInstance = new ThreadUtilsWrapper();
- } else {
- sInstance = sFactory.create();
- }
- }
- return sInstance;
- }
-
- public void runOnUiThread(Runnable r) {
- ThreadUtils.runOnUiThread(r);
- }
-
- /**
- * Instantiate this to explain how to create a ThreadUtilsWrapper instance in
- * ThreadUtilsWrapper.getInstance().
- */
- public interface Factory { public ThreadUtilsWrapper create(); }
-
- /**
- * Call this to use a different subclass of ThreadUtilsWrapper throughout the program.
- */
- public static void setFactory(Factory factory) {
- sFactory = factory;
- sInstance = null;
- }
- }
-
- /**
- * Wraps android.bluetooth.BluetoothAdapter.
- */
- static class BluetoothAdapterWrapper {
- private final BluetoothAdapter mAdapter;
- protected final Context mContext;
- protected BluetoothLeScannerWrapper mScannerWrapper;
-
- /**
- * Creates a BluetoothAdapterWrapper using the default
- * android.bluetooth.BluetoothAdapter. May fail if the default adapter
- * is not available or if the application does not have sufficient
- * permissions.
- */
- @CalledByNative("BluetoothAdapterWrapper")
- public static BluetoothAdapterWrapper createWithDefaultAdapter() {
- final boolean hasMinAPI = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
- if (!hasMinAPI) {
- Log.i(TAG, "BluetoothAdapterWrapper.create failed: SDK version (%d) too low.",
- Build.VERSION.SDK_INT);
- return null;
- }
-
- final boolean hasPermissions =
- ContextUtils.getApplicationContext().checkCallingOrSelfPermission(
- Manifest.permission.BLUETOOTH)
- == PackageManager.PERMISSION_GRANTED
- && ContextUtils.getApplicationContext().checkCallingOrSelfPermission(
- Manifest.permission.BLUETOOTH_ADMIN)
- == PackageManager.PERMISSION_GRANTED;
- if (!hasPermissions) {
- Log.w(TAG, "BluetoothAdapterWrapper.create failed: Lacking Bluetooth permissions.");
- return null;
- }
-
- // Only Low Energy currently supported, see BluetoothAdapterAndroid class note.
- final boolean hasLowEnergyFeature =
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2
- && ContextUtils.getApplicationContext().getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_BLUETOOTH_LE);
- if (!hasLowEnergyFeature) {
- Log.i(TAG, "BluetoothAdapterWrapper.create failed: No Low Energy support.");
- return null;
- }
-
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- if (adapter == null) {
- Log.i(TAG, "BluetoothAdapterWrapper.create failed: Default adapter not found.");
- return null;
- } else {
- return new BluetoothAdapterWrapper(adapter, ContextUtils.getApplicationContext());
- }
- }
-
- public BluetoothAdapterWrapper(BluetoothAdapter adapter, Context context) {
- mAdapter = adapter;
- mContext = context;
- }
-
- public boolean disable() {
- return mAdapter.disable();
- }
-
- public boolean enable() {
- return mAdapter.enable();
- }
-
- @SuppressLint("HardwareIds")
- public String getAddress() {
- return mAdapter.getAddress();
- }
-
- public BluetoothLeScannerWrapper getBluetoothLeScanner() {
- BluetoothLeScanner scanner = mAdapter.getBluetoothLeScanner();
- if (scanner == null) {
- return null;
- }
- if (mScannerWrapper == null) {
- mScannerWrapper = new BluetoothLeScannerWrapper(scanner);
- }
- return mScannerWrapper;
- }
-
- public Context getContext() {
- return mContext;
- }
-
- public String getName() {
- return mAdapter.getName();
- }
-
- public int getScanMode() {
- return mAdapter.getScanMode();
- }
-
- public boolean isDiscovering() {
- return mAdapter.isDiscovering();
- }
-
- public boolean isEnabled() {
- return mAdapter.isEnabled();
- }
- }
-
- /**
- * Wraps android.bluetooth.BluetoothLeScanner.
- */
- static class BluetoothLeScannerWrapper {
- protected final BluetoothLeScanner mScanner;
- private final HashMap<ScanCallbackWrapper, ForwardScanCallbackToWrapper> mCallbacks;
-
- public BluetoothLeScannerWrapper(BluetoothLeScanner scanner) {
- mScanner = scanner;
- mCallbacks = new HashMap<ScanCallbackWrapper, ForwardScanCallbackToWrapper>();
- }
-
- public void startScan(
- List<ScanFilter> filters, int scanSettingsScanMode, ScanCallbackWrapper callback) {
- ScanSettings settings =
- new ScanSettings.Builder().setScanMode(scanSettingsScanMode).build();
-
- ForwardScanCallbackToWrapper callbackForwarder =
- new ForwardScanCallbackToWrapper(callback);
- mCallbacks.put(callback, callbackForwarder);
-
- mScanner.startScan(filters, settings, callbackForwarder);
- }
-
- public void stopScan(ScanCallbackWrapper callback) {
- ForwardScanCallbackToWrapper callbackForwarder = mCallbacks.remove(callback);
- mScanner.stopScan(callbackForwarder);
- }
- }
-
- /**
- * Implements android.bluetooth.le.ScanCallback and forwards calls through to a
- * provided ScanCallbackWrapper instance.
- *
- * This class is required so that Fakes can use ScanCallbackWrapper without
- * it extending from ScanCallback. Fakes must function even on Android
- * versions where ScanCallback class is not defined.
- */
- static class ForwardScanCallbackToWrapper extends ScanCallback {
- final ScanCallbackWrapper mWrapperCallback;
-
- ForwardScanCallbackToWrapper(ScanCallbackWrapper wrapperCallback) {
- mWrapperCallback = wrapperCallback;
- }
-
- @Override
- public void onBatchScanResults(List<ScanResult> results) {
- ArrayList<ScanResultWrapper> resultsWrapped =
- new ArrayList<ScanResultWrapper>(results.size());
- for (ScanResult result : results) {
- resultsWrapped.add(new ScanResultWrapper(result));
- }
- mWrapperCallback.onBatchScanResult(resultsWrapped);
- }
-
- @Override
- public void onScanResult(int callbackType, ScanResult result) {
- mWrapperCallback.onScanResult(callbackType, new ScanResultWrapper(result));
- }
-
- @Override
- public void onScanFailed(int errorCode) {
- mWrapperCallback.onScanFailed(errorCode);
- }
- }
-
- /**
- * Wraps android.bluetooth.le.ScanCallback, being called by ScanCallbackImpl.
- */
- abstract static class ScanCallbackWrapper {
- public abstract void onBatchScanResult(List<ScanResultWrapper> results);
- public abstract void onScanResult(int callbackType, ScanResultWrapper result);
- public abstract void onScanFailed(int errorCode);
- }
-
- /**
- * Wraps android.bluetooth.le.ScanResult.
- */
- static class ScanResultWrapper {
- private final ScanResult mScanResult;
-
- public ScanResultWrapper(ScanResult scanResult) {
- mScanResult = scanResult;
- }
-
- public BluetoothDeviceWrapper getDevice() {
- return new BluetoothDeviceWrapper(mScanResult.getDevice());
- }
-
- public int getRssi() {
- return mScanResult.getRssi();
- }
-
- public List<ParcelUuid> getScanRecord_getServiceUuids() {
- return mScanResult.getScanRecord().getServiceUuids();
- }
-
- public Map<ParcelUuid, byte[]> getScanRecord_getServiceData() {
- return mScanResult.getScanRecord().getServiceData();
- }
-
- public SparseArray<byte[]> getScanRecord_getManufacturerSpecificData() {
- return mScanResult.getScanRecord().getManufacturerSpecificData();
- }
-
- public int getScanRecord_getTxPowerLevel() {
- return mScanResult.getScanRecord().getTxPowerLevel();
- }
-
- public String getScanRecord_getDeviceName() {
- return mScanResult.getScanRecord().getDeviceName();
- }
-
- public int getScanRecord_getAdvertiseFlags() {
- return mScanResult.getScanRecord().getAdvertiseFlags();
- }
- }
-
- /**
- * Wraps android.bluetooth.BluetoothDevice.
- */
- static class BluetoothDeviceWrapper {
- private final BluetoothDevice mDevice;
- private final HashMap<BluetoothGattCharacteristic, BluetoothGattCharacteristicWrapper>
- mCharacteristicsToWrappers;
- private final HashMap<BluetoothGattDescriptor, BluetoothGattDescriptorWrapper>
- mDescriptorsToWrappers;
-
- public BluetoothDeviceWrapper(BluetoothDevice device) {
- mDevice = device;
- mCharacteristicsToWrappers =
- new HashMap<BluetoothGattCharacteristic, BluetoothGattCharacteristicWrapper>();
- mDescriptorsToWrappers =
- new HashMap<BluetoothGattDescriptor, BluetoothGattDescriptorWrapper>();
- }
-
- public BluetoothGattWrapper connectGatt(Context context, boolean autoConnect,
- BluetoothGattCallbackWrapper callback, int transport) {
- return new BluetoothGattWrapper(
- mDevice.connectGatt(context, autoConnect,
- new ForwardBluetoothGattCallbackToWrapper(callback, this), transport),
- this);
- }
-
- public String getAddress() {
- return mDevice.getAddress();
- }
-
- public int getBluetoothClass_getDeviceClass() {
- if (mDevice == null || mDevice.getBluetoothClass() == null) {
- // BluetoothDevice.getBluetoothClass() returns null if adapter has been powered off.
- // Return DEVICE_CLASS_UNSPECIFIED in these cases.
- return DEVICE_CLASS_UNSPECIFIED;
- }
- return mDevice.getBluetoothClass().getDeviceClass();
- }
-
- public int getBondState() {
- return mDevice.getBondState();
- }
-
- public String getName() {
- return mDevice.getName();
- }
- }
-
- /**
- * Wraps android.bluetooth.BluetoothGatt.
- */
- static class BluetoothGattWrapper {
- private final BluetoothGatt mGatt;
- private final BluetoothDeviceWrapper mDeviceWrapper;
-
- BluetoothGattWrapper(BluetoothGatt gatt, BluetoothDeviceWrapper deviceWrapper) {
- mGatt = gatt;
- mDeviceWrapper = deviceWrapper;
- }
-
- public void disconnect() {
- mGatt.disconnect();
- }
-
- public void close() {
- mGatt.close();
- }
-
- public void discoverServices() {
- mGatt.discoverServices();
- }
-
- public List<BluetoothGattServiceWrapper> getServices() {
- List<BluetoothGattService> services = mGatt.getServices();
- ArrayList<BluetoothGattServiceWrapper> servicesWrapped =
- new ArrayList<BluetoothGattServiceWrapper>(services.size());
- for (BluetoothGattService service : services) {
- servicesWrapped.add(new BluetoothGattServiceWrapper(service, mDeviceWrapper));
- }
- return servicesWrapped;
- }
-
- boolean readCharacteristic(BluetoothGattCharacteristicWrapper characteristic) {
- return mGatt.readCharacteristic(characteristic.mCharacteristic);
- }
-
- boolean setCharacteristicNotification(
- BluetoothGattCharacteristicWrapper characteristic, boolean enable) {
- return mGatt.setCharacteristicNotification(characteristic.mCharacteristic, enable);
- }
-
- boolean writeCharacteristic(BluetoothGattCharacteristicWrapper characteristic) {
- return mGatt.writeCharacteristic(characteristic.mCharacteristic);
- }
-
- boolean readDescriptor(BluetoothGattDescriptorWrapper descriptor) {
- return mGatt.readDescriptor(descriptor.mDescriptor);
- }
-
- boolean writeDescriptor(BluetoothGattDescriptorWrapper descriptor) {
- return mGatt.writeDescriptor(descriptor.mDescriptor);
- }
- }
-
- /**
- * Implements android.bluetooth.BluetoothGattCallback and forwards calls through
- * to a provided BluetoothGattCallbackWrapper instance.
- *
- * This class is required so that Fakes can use BluetoothGattCallbackWrapper
- * without it extending from BluetoothGattCallback. Fakes must function even on
- * Android versions where BluetoothGattCallback class is not defined.
- */
- static class ForwardBluetoothGattCallbackToWrapper extends BluetoothGattCallback {
- final BluetoothGattCallbackWrapper mWrapperCallback;
- final BluetoothDeviceWrapper mDeviceWrapper;
-
- ForwardBluetoothGattCallbackToWrapper(BluetoothGattCallbackWrapper wrapperCallback,
- BluetoothDeviceWrapper deviceWrapper) {
- mWrapperCallback = wrapperCallback;
- mDeviceWrapper = deviceWrapper;
- }
-
- @Override
- public void onCharacteristicChanged(
- BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
- Log.i(TAG, "wrapper onCharacteristicChanged.");
- mWrapperCallback.onCharacteristicChanged(
- mDeviceWrapper.mCharacteristicsToWrappers.get(characteristic));
- }
-
- @Override
- public void onCharacteristicRead(
- BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
- mWrapperCallback.onCharacteristicRead(
- mDeviceWrapper.mCharacteristicsToWrappers.get(characteristic), status);
- }
-
- @Override
- public void onCharacteristicWrite(
- BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
- mWrapperCallback.onCharacteristicWrite(
- mDeviceWrapper.mCharacteristicsToWrappers.get(characteristic), status);
- }
-
- @Override
- public void onDescriptorRead(
- BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
- mWrapperCallback.onDescriptorRead(
- mDeviceWrapper.mDescriptorsToWrappers.get(descriptor), status);
- }
-
- @Override
- public void onDescriptorWrite(
- BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
- mWrapperCallback.onDescriptorWrite(
- mDeviceWrapper.mDescriptorsToWrappers.get(descriptor), status);
- }
-
- @Override
- public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
- mWrapperCallback.onConnectionStateChange(status, newState);
- }
-
- @Override
- public void onServicesDiscovered(BluetoothGatt gatt, int status) {
- mWrapperCallback.onServicesDiscovered(status);
- }
- }
-
- /**
- * Wrapper alternative to android.bluetooth.BluetoothGattCallback allowing clients and Fakes to
- * work on older SDK versions without having a dependency on the class not defined there.
- *
- * BluetoothGatt gatt parameters are omitted from methods as each call would
- * need to look up the correct BluetoothGattWrapper instance.
- * Client code should cache the BluetoothGattWrapper provided if
- * necessary from the initial BluetoothDeviceWrapper.connectGatt
- * call.
- */
- abstract static class BluetoothGattCallbackWrapper {
- public abstract void onCharacteristicChanged(
- BluetoothGattCharacteristicWrapper characteristic);
- public abstract void onCharacteristicRead(
- BluetoothGattCharacteristicWrapper characteristic, int status);
- public abstract void onCharacteristicWrite(
- BluetoothGattCharacteristicWrapper characteristic, int status);
- public abstract void onDescriptorRead(
- BluetoothGattDescriptorWrapper descriptor, int status);
- public abstract void onDescriptorWrite(
- BluetoothGattDescriptorWrapper descriptor, int status);
- public abstract void onConnectionStateChange(int status, int newState);
- public abstract void onServicesDiscovered(int status);
- }
-
- /**
- * Wraps android.bluetooth.BluetoothGattService.
- */
- static class BluetoothGattServiceWrapper {
- private final BluetoothGattService mService;
- private final BluetoothDeviceWrapper mDeviceWrapper;
-
- public BluetoothGattServiceWrapper(
- BluetoothGattService service, BluetoothDeviceWrapper deviceWrapper) {
- mService = service;
- mDeviceWrapper = deviceWrapper;
- }
-
- public List<BluetoothGattCharacteristicWrapper> getCharacteristics() {
- List<BluetoothGattCharacteristic> characteristics = mService.getCharacteristics();
- ArrayList<BluetoothGattCharacteristicWrapper> characteristicsWrapped =
- new ArrayList<BluetoothGattCharacteristicWrapper>(characteristics.size());
- for (BluetoothGattCharacteristic characteristic : characteristics) {
- BluetoothGattCharacteristicWrapper characteristicWrapper =
- mDeviceWrapper.mCharacteristicsToWrappers.get(characteristic);
- if (characteristicWrapper == null) {
- characteristicWrapper =
- new BluetoothGattCharacteristicWrapper(characteristic, mDeviceWrapper);
- mDeviceWrapper.mCharacteristicsToWrappers.put(
- characteristic, characteristicWrapper);
- }
- characteristicsWrapped.add(characteristicWrapper);
- }
- return characteristicsWrapped;
- }
-
- public int getInstanceId() {
- return mService.getInstanceId();
- }
-
- public UUID getUuid() {
- return mService.getUuid();
- }
- }
-
- /**
- * Wraps android.bluetooth.BluetoothGattCharacteristic.
- */
- static class BluetoothGattCharacteristicWrapper {
- final BluetoothGattCharacteristic mCharacteristic;
- final BluetoothDeviceWrapper mDeviceWrapper;
-
- public BluetoothGattCharacteristicWrapper(
- BluetoothGattCharacteristic characteristic, BluetoothDeviceWrapper deviceWrapper) {
- mCharacteristic = characteristic;
- mDeviceWrapper = deviceWrapper;
- }
-
- public List<BluetoothGattDescriptorWrapper> getDescriptors() {
- List<BluetoothGattDescriptor> descriptors = mCharacteristic.getDescriptors();
-
- ArrayList<BluetoothGattDescriptorWrapper> descriptorsWrapped =
- new ArrayList<BluetoothGattDescriptorWrapper>(descriptors.size());
-
- for (BluetoothGattDescriptor descriptor : descriptors) {
- BluetoothGattDescriptorWrapper descriptorWrapper =
- mDeviceWrapper.mDescriptorsToWrappers.get(descriptor);
- if (descriptorWrapper == null) {
- descriptorWrapper =
- new BluetoothGattDescriptorWrapper(descriptor, mDeviceWrapper);
- mDeviceWrapper.mDescriptorsToWrappers.put(descriptor, descriptorWrapper);
- }
- descriptorsWrapped.add(descriptorWrapper);
- }
- return descriptorsWrapped;
- }
-
- public int getInstanceId() {
- return mCharacteristic.getInstanceId();
- }
-
- public int getProperties() {
- return mCharacteristic.getProperties();
- }
-
- public UUID getUuid() {
- return mCharacteristic.getUuid();
- }
-
- public byte[] getValue() {
- return mCharacteristic.getValue();
- }
-
- public boolean setValue(byte[] value) {
- return mCharacteristic.setValue(value);
- }
-
- public void setWriteType(int writeType) {
- mCharacteristic.setWriteType(writeType);
- }
- }
-
- /**
- * Wraps android.bluetooth.BluetoothGattDescriptor.
- */
- static class BluetoothGattDescriptorWrapper {
- private final BluetoothGattDescriptor mDescriptor;
- final BluetoothDeviceWrapper mDeviceWrapper;
-
- public BluetoothGattDescriptorWrapper(
- BluetoothGattDescriptor descriptor, BluetoothDeviceWrapper deviceWrapper) {
- mDescriptor = descriptor;
- mDeviceWrapper = deviceWrapper;
- }
-
- public BluetoothGattCharacteristicWrapper getCharacteristic() {
- return mDeviceWrapper.mCharacteristicsToWrappers.get(mDescriptor.getCharacteristic());
- }
-
- public UUID getUuid() {
- return mDescriptor.getUuid();
- }
-
- public byte[] getValue() {
- return mDescriptor.getValue();
- }
-
- public boolean setValue(byte[] value) {
- return mDescriptor.setValue(value);
- }
- }
-}
diff --git a/chromium/device/bluetooth/bluetooth_adapter.h b/chromium/device/bluetooth/bluetooth_adapter.h
index 415c01c72a4..16e00340842 100644
--- a/chromium/device/bluetooth/bluetooth_adapter.h
+++ b/chromium/device/bluetooth/bluetooth_adapter.h
@@ -19,6 +19,7 @@
#include "base/containers/queue.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "device/bluetooth/bluetooth_advertisement.h"
@@ -311,9 +312,9 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothAdapter
ServiceOptions();
~ServiceOptions();
- std::unique_ptr<int> channel;
- std::unique_ptr<int> psm;
- std::unique_ptr<std::string> name;
+ base::Optional<int> channel;
+ base::Optional<int> psm;
+ base::Optional<std::string> name;
};
// The ErrorCallback is used for methods that can fail in which case it is
diff --git a/chromium/device/bluetooth/bluetooth_adapter_factory_wrapper.cc b/chromium/device/bluetooth/bluetooth_adapter_factory_wrapper.cc
deleted file mode 100644
index 3a3441fc48a..00000000000
--- a/chromium/device/bluetooth/bluetooth_adapter_factory_wrapper.cc
+++ /dev/null
@@ -1,150 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "device/bluetooth/bluetooth_adapter_factory_wrapper.h"
-
-#include <stddef.h>
-
-#include <utility>
-
-#include "base/bind.h"
-#include "base/location.h"
-#include "base/threading/thread_task_runner_handle.h"
-#include "device/bluetooth/bluetooth_adapter_factory.h"
-
-namespace {
-
-static base::LazyInstance<device::BluetoothAdapterFactoryWrapper>::Leaky
- g_bluetooth_adapter_factory_wrapper_singleton = LAZY_INSTANCE_INITIALIZER;
-
-} // namespace
-
-namespace device {
-
-BluetoothAdapterFactoryWrapper::~BluetoothAdapterFactoryWrapper() {
- DCHECK(thread_checker_.CalledOnValidThread());
- // All observers should have been removed already.
- DCHECK(adapter_observers_.empty());
- // Clear adapter.
- set_adapter(scoped_refptr<BluetoothAdapter>());
-}
-
-// static
-BluetoothAdapterFactoryWrapper& BluetoothAdapterFactoryWrapper::Get() {
- return g_bluetooth_adapter_factory_wrapper_singleton.Get();
-}
-
-bool BluetoothAdapterFactoryWrapper::IsLowEnergySupported() {
- DCHECK(thread_checker_.CalledOnValidThread());
- if (adapter_ != nullptr) {
- return true;
- }
- return BluetoothAdapterFactory::Get()->IsLowEnergySupported();
-}
-
-void BluetoothAdapterFactoryWrapper::AcquireAdapter(
- BluetoothAdapter::Observer* observer,
- AcquireAdapterCallback callback) {
- DCHECK(thread_checker_.CalledOnValidThread());
- DCHECK(!GetAdapter(observer));
-
- AddAdapterObserver(observer);
- if (adapter_) {
- base::ThreadTaskRunnerHandle::Get()->PostTask(
- FROM_HERE, base::BindOnce(std::move(callback), adapter_));
- return;
- }
-
- DCHECK(BluetoothAdapterFactory::Get()->IsLowEnergySupported());
- BluetoothAdapterFactory::Get()->GetAdapter(
- base::BindOnce(&BluetoothAdapterFactoryWrapper::OnGetAdapter,
- weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
-}
-
-void BluetoothAdapterFactoryWrapper::ReleaseAdapter(
- BluetoothAdapter::Observer* observer) {
- DCHECK(thread_checker_.CalledOnValidThread());
- if (!HasAdapter(observer)) {
- return;
- }
- RemoveAdapterObserver(observer);
- if (adapter_observers_.empty())
- set_adapter(scoped_refptr<BluetoothAdapter>());
-}
-
-BluetoothAdapter* BluetoothAdapterFactoryWrapper::GetAdapter(
- BluetoothAdapter::Observer* observer) {
- DCHECK(thread_checker_.CalledOnValidThread());
- if (HasAdapter(observer)) {
- return adapter_.get();
- }
- return nullptr;
-}
-
-void BluetoothAdapterFactoryWrapper::SetBluetoothAdapterForTesting(
- scoped_refptr<BluetoothAdapter> mock_adapter) {
- DCHECK(thread_checker_.CalledOnValidThread());
- set_adapter(std::move(mock_adapter));
-}
-
-BluetoothAdapterFactoryWrapper::BluetoothAdapterFactoryWrapper() {
- DCHECK(thread_checker_.CalledOnValidThread());
-}
-
-void BluetoothAdapterFactoryWrapper::OnGetAdapter(
- AcquireAdapterCallback continuation,
- scoped_refptr<BluetoothAdapter> adapter) {
- DCHECK(thread_checker_.CalledOnValidThread());
-
- set_adapter(adapter);
- std::move(continuation).Run(adapter_);
-}
-
-bool BluetoothAdapterFactoryWrapper::HasAdapter(
- BluetoothAdapter::Observer* observer) {
- DCHECK(thread_checker_.CalledOnValidThread());
-
- return base::Contains(adapter_observers_, observer);
-}
-
-void BluetoothAdapterFactoryWrapper::AddAdapterObserver(
- BluetoothAdapter::Observer* observer) {
- DCHECK(thread_checker_.CalledOnValidThread());
-
- auto iter = adapter_observers_.insert(observer);
- DCHECK(iter.second);
- if (adapter_) {
- adapter_->AddObserver(observer);
- }
-}
-
-void BluetoothAdapterFactoryWrapper::RemoveAdapterObserver(
- BluetoothAdapter::Observer* observer) {
- DCHECK(thread_checker_.CalledOnValidThread());
-
- size_t removed = adapter_observers_.erase(observer);
- DCHECK(removed);
- if (adapter_) {
- adapter_->RemoveObserver(observer);
- }
-}
-
-void BluetoothAdapterFactoryWrapper::set_adapter(
- scoped_refptr<BluetoothAdapter> adapter) {
- DCHECK(thread_checker_.CalledOnValidThread());
-
- if (adapter_.get()) {
- for (BluetoothAdapter::Observer* observer : adapter_observers_) {
- adapter_->RemoveObserver(observer);
- }
- }
- adapter_ = adapter;
- if (adapter_.get()) {
- for (BluetoothAdapter::Observer* observer : adapter_observers_) {
- adapter_->AddObserver(observer);
- }
- }
-}
-
-} // namespace device
diff --git a/chromium/device/bluetooth/bluetooth_adapter_factory_wrapper.h b/chromium/device/bluetooth/bluetooth_adapter_factory_wrapper.h
deleted file mode 100644
index 74ce72d9300..00000000000
--- a/chromium/device/bluetooth/bluetooth_adapter_factory_wrapper.h
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef DEVICE_BLUETOOTH_BLUETOOTH_ADAPTER_FACTORY_WRAPPER_H_
-#define DEVICE_BLUETOOTH_BLUETOOTH_ADAPTER_FACTORY_WRAPPER_H_
-
-#include <unordered_set>
-
-#include "base/lazy_instance.h"
-#include "base/macros.h"
-#include "base/threading/thread_checker.h"
-#include "device/bluetooth/bluetooth_adapter.h"
-#include "device/bluetooth/bluetooth_export.h"
-
-namespace device {
-
-// Wrapper around BluetoothAdapterFactory that allows us to change
-// the underlying BluetoothAdapter object and have the observers
-// observe the new instance of the object.
-// TODO(ortuno): Once there is no need to swap the adapter to change its
-// behavior observers should add/remove themselves to/from the adapter.
-// http://crbug.com/603291
-class DEVICE_BLUETOOTH_EXPORT BluetoothAdapterFactoryWrapper {
- public:
- using AcquireAdapterCallback =
- base::OnceCallback<void(scoped_refptr<BluetoothAdapter>)>;
-
- ~BluetoothAdapterFactoryWrapper();
-
- static BluetoothAdapterFactoryWrapper& Get();
-
- // Returns true if the platform supports Bluetooth Low Energy or if
- // SetBluetoothAdapterForTesting has been called.
- bool IsLowEnergySupported();
-
- // Adds |observer| to the set of adapter observers. If another observer has
- // acquired the adapter in the past it adds |observer| as an observer to that
- // adapter, otherwise it gets a new adapter and adds |observer| to it. Runs
- // |callback| with the adapter |observer| has been added to.
- void AcquireAdapter(BluetoothAdapter::Observer* observer,
- AcquireAdapterCallback callback);
- // Removes |observer| from the list of adapter observers if |observer|
- // has acquired the adapter in the past. If there are no more observers
- // it deletes the reference to the adapter.
- void ReleaseAdapter(BluetoothAdapter::Observer* observer);
-
- // Returns an adapter if |observer| has acquired an adapter in the past and
- // this instance holds a reference to an adapter. Otherwise returns nullptr.
- BluetoothAdapter* GetAdapter(BluetoothAdapter::Observer* observer);
-
- // Sets a new BluetoothAdapter to be returned by GetAdapter. When setting
- // a new adapter all observers from the old adapter are removed and added
- // to |mock_adapter|.
- void SetBluetoothAdapterForTesting(
- scoped_refptr<BluetoothAdapter> mock_adapter);
-
- private:
- // friend LazyInstance to permit access to private constructor.
- friend base::LazyInstanceTraitsBase<BluetoothAdapterFactoryWrapper>;
-
- BluetoothAdapterFactoryWrapper();
-
- void OnGetAdapter(AcquireAdapterCallback continuation,
- scoped_refptr<BluetoothAdapter> adapter);
-
- bool HasAdapter(BluetoothAdapter::Observer* observer);
- void AddAdapterObserver(BluetoothAdapter::Observer* observer);
- void RemoveAdapterObserver(BluetoothAdapter::Observer* observer);
-
- // Sets |adapter_| to a BluetoothAdapter instance and register observers,
- // releasing references to previous |adapter_|.
- void set_adapter(scoped_refptr<BluetoothAdapter> adapter);
-
- // A BluetoothAdapter instance representing an adapter of the system.
- scoped_refptr<BluetoothAdapter> adapter_;
-
- // We keep a list of all observers so that when the adapter gets swapped,
- // we can remove all observers from the old adapter and add them to the
- // new adapter.
- std::unordered_set<BluetoothAdapter::Observer*> adapter_observers_;
-
- // Should only be called on the UI thread.
- base::ThreadChecker thread_checker_;
-
- // Weak pointer factory for generating 'this' pointers that might live longer
- // than we do.
- // Note: This should remain the last member so it'll be destroyed and
- // invalidate its weak pointers before any other members are destroyed.
- base::WeakPtrFactory<BluetoothAdapterFactoryWrapper> weak_ptr_factory_{this};
-
- DISALLOW_COPY_AND_ASSIGN(BluetoothAdapterFactoryWrapper);
-};
-
-} // namespace device
-
-#endif // DEVICE_BLUETOOTH_BLUETOOTH_ADAPTER_FACTORY_WRAPPER_H_
diff --git a/chromium/device/bluetooth/bluetooth_adapter_winrt.cc b/chromium/device/bluetooth/bluetooth_adapter_winrt.cc
index a51515dee10..5f2c36eb504 100644
--- a/chromium/device/bluetooth/bluetooth_adapter_winrt.cc
+++ b/chromium/device/bluetooth/bluetooth_adapter_winrt.cc
@@ -31,7 +31,6 @@
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
-#include "base/threading/scoped_thread_priority.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/win/core_winrt_util.h"
#include "base/win/post_async_results.h"
@@ -660,7 +659,9 @@ void BluetoothAdapterWinrt::Initialize(base::OnceClosure init_callback) {
// Some of the initialization work requires loading libraries and should not
// be run on the browser main thread.
base::ThreadPool::PostTaskAndReplyWithResult(
- FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
+ FROM_HERE,
+ {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+ base::ThreadPolicy::MUST_USE_FOREGROUND},
base::BindOnce(&BluetoothAdapterWinrt::PerformSlowInitTasks),
base::BindOnce(&BluetoothAdapterWinrt::CompleteInitAgile,
weak_ptr_factory_.GetWeakPtr(), std::move(init_callback)));
@@ -700,10 +701,6 @@ void BluetoothAdapterWinrt::InitForTests(
// static
BluetoothAdapterWinrt::StaticsInterfaces
BluetoothAdapterWinrt::PerformSlowInitTasks() {
- // Mitigate the issues caused by loading DLLs on a background thread
- // (http://crbug/973868).
- SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
-
if (!ResolveCoreWinRT())
return BluetoothAdapterWinrt::StaticsInterfaces();
diff --git a/chromium/device/bluetooth/bluetooth_classic_device_mac.h b/chromium/device/bluetooth/bluetooth_classic_device_mac.h
index b7ed051bac5..68fab556112 100644
--- a/chromium/device/bluetooth/bluetooth_classic_device_mac.h
+++ b/chromium/device/bluetooth/bluetooth_classic_device_mac.h
@@ -33,6 +33,7 @@ class BluetoothClassicDeviceMac : public BluetoothDeviceMac {
// BluetoothDevice override
uint32_t GetBluetoothClass() const override;
std::string GetAddress() const override;
+ AddressType GetAddressType() const override;
VendorIDSource GetVendorIDSource() const override;
uint16_t GetVendorID() const override;
uint16_t GetProductID() const override;
diff --git a/chromium/device/bluetooth/bluetooth_classic_device_mac.mm b/chromium/device/bluetooth/bluetooth_classic_device_mac.mm
index 839aca94bc9..43bac24c872 100644
--- a/chromium/device/bluetooth/bluetooth_classic_device_mac.mm
+++ b/chromium/device/bluetooth/bluetooth_classic_device_mac.mm
@@ -85,6 +85,11 @@ std::string BluetoothClassicDeviceMac::GetAddress() const {
return GetDeviceAddress(device_);
}
+BluetoothDevice::AddressType BluetoothClassicDeviceMac::GetAddressType() const {
+ NOTIMPLEMENTED();
+ return ADDR_TYPE_UNKNOWN;
+}
+
BluetoothDevice::VendorIDSource BluetoothClassicDeviceMac::GetVendorIDSource()
const {
return VENDOR_ID_UNKNOWN;
diff --git a/chromium/device/bluetooth/bluetooth_device.h b/chromium/device/bluetooth/bluetooth_device.h
index 643cad1fcfd..31aa41b3d95 100644
--- a/chromium/device/bluetooth/bluetooth_device.h
+++ b/chromium/device/bluetooth/bluetooth_device.h
@@ -60,6 +60,13 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDevice {
VENDOR_ID_MAX_VALUE = VENDOR_ID_USB
};
+ // Possible values that may be returned by GetAddressType().
+ enum AddressType {
+ ADDR_TYPE_UNKNOWN,
+ ADDR_TYPE_PUBLIC,
+ ADDR_TYPE_RANDOM,
+ };
+
// The value returned if the RSSI or transmit power cannot be read.
static const int kUnknownPower = 127;
// The value returned if the appearance is not present.
@@ -219,6 +226,10 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDevice {
// a unique key to identify the device and copied where needed.
virtual std::string GetAddress() const = 0;
+ // Returns the Bluetooth address type of the device. Currently available on
+ // Linux and Chrome OS.
+ virtual AddressType GetAddressType() const = 0;
+
// Returns the allocation source of the identifier returned by GetVendorID(),
// where available, or VENDOR_ID_UNKNOWN where not.
virtual VendorIDSource GetVendorIDSource() const = 0;
diff --git a/chromium/device/bluetooth/bluetooth_device_android.cc b/chromium/device/bluetooth/bluetooth_device_android.cc
index 9e25f6a0df4..27347ad06bb 100644
--- a/chromium/device/bluetooth/bluetooth_device_android.cc
+++ b/chromium/device/bluetooth/bluetooth_device_android.cc
@@ -67,6 +67,11 @@ std::string BluetoothDeviceAndroid::GetAddress() const {
Java_ChromeBluetoothDevice_getAddress(AttachCurrentThread(), j_device_));
}
+BluetoothDevice::AddressType BluetoothDeviceAndroid::GetAddressType() const {
+ NOTIMPLEMENTED();
+ return ADDR_TYPE_UNKNOWN;
+}
+
BluetoothDevice::VendorIDSource BluetoothDeviceAndroid::GetVendorIDSource()
const {
// Android API does not provide Vendor ID.
diff --git a/chromium/device/bluetooth/bluetooth_device_android.h b/chromium/device/bluetooth/bluetooth_device_android.h
index bfeff535c83..d6dc20f990a 100644
--- a/chromium/device/bluetooth/bluetooth_device_android.h
+++ b/chromium/device/bluetooth/bluetooth_device_android.h
@@ -50,6 +50,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDeviceAndroid final
// BluetoothDevice:
uint32_t GetBluetoothClass() const override;
std::string GetAddress() const override;
+ AddressType GetAddressType() const override;
VendorIDSource GetVendorIDSource() const override;
uint16_t GetVendorID() const override;
uint16_t GetProductID() const override;
diff --git a/chromium/device/bluetooth/bluetooth_device_win.cc b/chromium/device/bluetooth/bluetooth_device_win.cc
index 82b7e0f9ec0..17bcbabebe4 100644
--- a/chromium/device/bluetooth/bluetooth_device_win.cc
+++ b/chromium/device/bluetooth/bluetooth_device_win.cc
@@ -63,6 +63,11 @@ std::string BluetoothDeviceWin::GetAddress() const {
return address_;
}
+BluetoothDevice::AddressType BluetoothDeviceWin::GetAddressType() const {
+ NOTIMPLEMENTED();
+ return ADDR_TYPE_UNKNOWN;
+}
+
BluetoothDevice::VendorIDSource
BluetoothDeviceWin::GetVendorIDSource() const {
return VENDOR_ID_UNKNOWN;
diff --git a/chromium/device/bluetooth/bluetooth_device_win.h b/chromium/device/bluetooth/bluetooth_device_win.h
index e55dbde0120..966925107c9 100644
--- a/chromium/device/bluetooth/bluetooth_device_win.h
+++ b/chromium/device/bluetooth/bluetooth_device_win.h
@@ -42,6 +42,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDeviceWin
// BluetoothDevice override
uint32_t GetBluetoothClass() const override;
std::string GetAddress() const override;
+ AddressType GetAddressType() const override;
VendorIDSource GetVendorIDSource() const override;
uint16_t GetVendorID() const override;
uint16_t GetProductID() const override;
diff --git a/chromium/device/bluetooth/bluetooth_device_winrt.cc b/chromium/device/bluetooth/bluetooth_device_winrt.cc
index 482c9f157e7..6ba09a2da58 100644
--- a/chromium/device/bluetooth/bluetooth_device_winrt.cc
+++ b/chromium/device/bluetooth/bluetooth_device_winrt.cc
@@ -208,6 +208,11 @@ std::string BluetoothDeviceWinrt::GetAddress() const {
return address_;
}
+BluetoothDevice::AddressType BluetoothDeviceWinrt::GetAddressType() const {
+ NOTIMPLEMENTED();
+ return ADDR_TYPE_UNKNOWN;
+}
+
BluetoothDevice::VendorIDSource BluetoothDeviceWinrt::GetVendorIDSource()
const {
NOTIMPLEMENTED();
diff --git a/chromium/device/bluetooth/bluetooth_device_winrt.h b/chromium/device/bluetooth/bluetooth_device_winrt.h
index 0062316191a..84cdbf9a15f 100644
--- a/chromium/device/bluetooth/bluetooth_device_winrt.h
+++ b/chromium/device/bluetooth/bluetooth_device_winrt.h
@@ -48,6 +48,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDeviceWinrt : public BluetoothDevice {
// BluetoothDevice:
uint32_t GetBluetoothClass() const override;
std::string GetAddress() const override;
+ AddressType GetAddressType() const override;
VendorIDSource GetVendorIDSource() const override;
uint16_t GetVendorID() const override;
uint16_t GetProductID() const override;
diff --git a/chromium/device/bluetooth/bluetooth_low_energy_device_mac.h b/chromium/device/bluetooth/bluetooth_low_energy_device_mac.h
index 83df27bc2fa..7adb0719af4 100644
--- a/chromium/device/bluetooth/bluetooth_low_energy_device_mac.h
+++ b/chromium/device/bluetooth/bluetooth_low_energy_device_mac.h
@@ -42,6 +42,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothLowEnergyDeviceMac
std::string GetIdentifier() const override;
uint32_t GetBluetoothClass() const override;
std::string GetAddress() const override;
+ AddressType GetAddressType() const override;
BluetoothDevice::VendorIDSource GetVendorIDSource() const override;
uint16_t GetVendorID() const override;
uint16_t GetProductID() const override;
diff --git a/chromium/device/bluetooth/bluetooth_low_energy_device_mac.mm b/chromium/device/bluetooth/bluetooth_low_energy_device_mac.mm
index df268cfbc42..95c6888cac4 100644
--- a/chromium/device/bluetooth/bluetooth_low_energy_device_mac.mm
+++ b/chromium/device/bluetooth/bluetooth_low_energy_device_mac.mm
@@ -72,6 +72,12 @@ std::string BluetoothLowEnergyDeviceMac::GetAddress() const {
return hash_address_;
}
+BluetoothDevice::AddressType BluetoothLowEnergyDeviceMac::GetAddressType()
+ const {
+ NOTIMPLEMENTED();
+ return ADDR_TYPE_UNKNOWN;
+}
+
BluetoothDevice::VendorIDSource BluetoothLowEnergyDeviceMac::GetVendorIDSource()
const {
return VENDOR_ID_UNKNOWN;
diff --git a/chromium/device/bluetooth/bluetooth_socket_mac.mm b/chromium/device/bluetooth/bluetooth_socket_mac.mm
index 8bf819d4c1d..7a73cf871f1 100644
--- a/chromium/device/bluetooth/bluetooth_socket_mac.mm
+++ b/chromium/device/bluetooth/bluetooth_socket_mac.mm
@@ -20,6 +20,7 @@
#include "base/mac/scoped_cftyperef.h"
#include "base/memory/ref_counted.h"
#include "base/numerics/safe_conversions.h"
+#include "base/optional.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
@@ -245,7 +246,7 @@ NSString* IntToNSString(int integer) {
// corresponding to the provided |uuid|, |name|, and |protocol_definition|. Does
// not include a service name in the definition if |name| is null.
NSDictionary* BuildServiceDefinition(const BluetoothUUID& uuid,
- const std::string* name,
+ const base::Optional<std::string>& name,
NSArray* protocol_definition) {
NSMutableDictionary* service_definition = [NSMutableDictionary dictionary];
@@ -290,8 +291,7 @@ NSDictionary* BuildRfcommServiceDefinition(
},
],
];
- return BuildServiceDefinition(
- uuid, options.name.get(), rfcomm_protocol_definition);
+ return BuildServiceDefinition(uuid, options.name, rfcomm_protocol_definition);
}
// Returns a dictionary containing the Bluetooth L2CAP service definition
@@ -310,8 +310,7 @@ NSDictionary* BuildL2capServiceDefinition(
},
],
];
- return BuildServiceDefinition(
- uuid, options.name.get(), l2cap_protocol_definition);
+ return BuildServiceDefinition(uuid, options.name, l2cap_protocol_definition);
}
// Registers a Bluetooth service with the specified |service_definition| in the
@@ -340,7 +339,7 @@ IOBluetoothSDPServiceRecord* RegisterService(
// Returns true iff the |requested_channel_id| was registered in the RFCOMM
// |service_record|. If it was, also updates |registered_channel_id| with the
// registered value, as the requested id may have been left unspecified.
-bool VerifyRfcommService(const int* requested_channel_id,
+bool VerifyRfcommService(const base::Optional<int>& requested_channel_id,
BluetoothRFCOMMChannelID* registered_channel_id,
IOBluetoothSDPServiceRecord* service_record) {
// Test whether the requested channel id was available.
@@ -357,9 +356,9 @@ bool VerifyRfcommService(const int* requested_channel_id,
return true;
}
-// Registers an RFCOMM service with the specified |uuid|, |options.channel_id|,
+// Registers an RFCOMM service with the specified |uuid|, |options.channel|,
// and |options.name| in the system SDP server. Automatically allocates a
-// channel if |options.channel_id| is null. Does not specify a name if
+// channel if |options.channel| is null. Does not specify a name if
// |options.name| is null. Returns a handle to the registered service and
// updates |registered_channel_id| to the actual channel id, or returns nil if
// the service could not be registered.
@@ -369,14 +368,14 @@ IOBluetoothSDPServiceRecord* RegisterRfcommService(
BluetoothRFCOMMChannelID* registered_channel_id) {
return RegisterService(
BuildRfcommServiceDefinition(uuid, options),
- base::BindOnce(&VerifyRfcommService, options.channel.get(),
+ base::BindOnce(&VerifyRfcommService, options.channel,
registered_channel_id));
}
// Returns true iff the |requested_psm| was registered in the L2CAP
// |service_record|. If it was, also updates |registered_psm| with the
// registered value, as the requested PSM may have been left unspecified.
-bool VerifyL2capService(const int* requested_psm,
+bool VerifyL2capService(const base::Optional<int>& requested_psm,
BluetoothL2CAPPSM* registered_psm,
IOBluetoothSDPServiceRecord* service_record) {
// Test whether the requested PSM was available.
@@ -404,7 +403,7 @@ IOBluetoothSDPServiceRecord* RegisterL2capService(
BluetoothL2CAPPSM* registered_psm) {
return RegisterService(
BuildL2capServiceDefinition(uuid, options),
- base::BindOnce(&VerifyL2capService, options.psm.get(), registered_psm));
+ base::BindOnce(&VerifyL2capService, options.psm, registered_psm));
}
} // namespace
diff --git a/chromium/device/bluetooth/bluetooth_socket_net.cc b/chromium/device/bluetooth/bluetooth_socket_net.cc
index 7c95d63b491..7408e62fde2 100644
--- a/chromium/device/bluetooth/bluetooth_socket_net.cc
+++ b/chromium/device/bluetooth/bluetooth_socket_net.cc
@@ -238,14 +238,43 @@ void BluetoothSocketNet::SendFrontWriteRequest() {
if (!tcp_socket_)
return;
+ if (pending_write_request_) {
+ // It is possible to enter this function while a write request is
+ // currently pending if the following sequence happens:
+ //
+ // 1) A single pending write is running and it is the last one in the queue.
+ // 2) A Send() call queues a DoSend() call on the sequence.
+ // 3) The pending write completes, queue length is zero, a call to
+ // SendFrontWriteRequest is queued on the sequence.
+ // 4) DoSend() runs on the sequence, queues a write request, and runs
+ // SendFrontWriteRequest() inline because the queue size is 1.
+ // 5) The immediate call for SendFrontWriteRequest() starts a write request
+ // and exits.
+ // 6) The next SendFrontWriteRequest() which was queued in step 3 now runs
+ // while the write request from 5 is still pending.
+ //
+ // At this point we have entered SendFrontWriteRequest() while we are
+ // waiting for a pending write. Previously the code did not handle this
+ // situation and would attempt to process the write request at the front
+ // of the queue twice which is both wrong from a data perspective and also
+ // triggers a CHECK in the Socket.
+ //
+ // The fix is to ensure we only process a new write request when there are
+ // no pending requests, so we exit early here and let OnSocketWriteComplete
+ // queue the next SendFrontWriteRequest.
+ return;
+ }
+
if (write_queue_.size() == 0)
return;
- WriteRequest* request = write_queue_.front().get();
+ pending_write_request_ = std::move(write_queue_.front());
+ write_queue_.pop();
+
auto copyable_callback = base::AdaptCallbackForRepeating(
base::BindOnce(&BluetoothSocketNet::OnSocketWriteComplete, this,
- std::move(request->success_callback),
- std::move(request->error_callback)));
+ std::move(pending_write_request_->success_callback),
+ std::move(pending_write_request_->error_callback)));
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("bluetooth_socket", R"(
semantics {
@@ -270,9 +299,9 @@ void BluetoothSocketNet::SendFrontWriteRequest() {
"DeviceAllowBluetooth policy can disable Bluetooth for ChromeOS, "
"not implemented for other platforms."
})");
- int send_result =
- tcp_socket_->Write(request->buffer.get(), request->buffer_size,
- copyable_callback, traffic_annotation);
+ int send_result = tcp_socket_->Write(pending_write_request_->buffer.get(),
+ pending_write_request_->buffer_size,
+ copyable_callback, traffic_annotation);
// Write() will not have run |copyable_callback| if there is no pending I/O.
if (send_result != net::ERR_IO_PENDING)
copyable_callback.Run(send_result);
@@ -284,7 +313,7 @@ void BluetoothSocketNet::OnSocketWriteComplete(
int send_result) {
DCHECK(socket_thread_->task_runner()->RunsTasksInCurrentSequence());
- write_queue_.pop();
+ pending_write_request_.reset();
if (send_result >= net::OK) {
std::move(success_callback).Run(send_result);
diff --git a/chromium/device/bluetooth/bluetooth_socket_net.h b/chromium/device/bluetooth/bluetooth_socket_net.h
index 9c7459df616..45c070af327 100644
--- a/chromium/device/bluetooth/bluetooth_socket_net.h
+++ b/chromium/device/bluetooth/bluetooth_socket_net.h
@@ -109,6 +109,7 @@ class BluetoothSocketNet : public BluetoothSocket {
std::unique_ptr<net::TCPSocket> tcp_socket_;
scoped_refptr<net::IOBufferWithSize> read_buffer_;
base::queue<std::unique_ptr<WriteRequest>> write_queue_;
+ std::unique_ptr<WriteRequest> pending_write_request_;
DISALLOW_COPY_AND_ASSIGN(BluetoothSocketNet);
};
diff --git a/chromium/device/bluetooth/bluez/bluetooth_bluez_unittest.cc b/chromium/device/bluetooth/bluez/bluetooth_bluez_unittest.cc
index 8d954537d90..2896da6ccd5 100644
--- a/chromium/device/bluetooth/bluez/bluetooth_bluez_unittest.cc
+++ b/chromium/device/bluetooth/bluez/bluetooth_bluez_unittest.cc
@@ -1804,6 +1804,33 @@ TEST_F(BluetoothBlueZTest, DeviceProperties) {
EXPECT_EQ(0x0306, devices[idx]->GetDeviceID());
}
+TEST_F(BluetoothBlueZTest, DeviceAddressType) {
+ GetAdapter();
+ BluetoothAdapter::DeviceList devices = adapter_->GetDevices();
+ ASSERT_EQ(2U, devices.size());
+
+ int idx = GetDeviceIndexByAddress(
+ devices, bluez::FakeBluetoothDeviceClient::kPairedDeviceAddress);
+ ASSERT_NE(-1, idx);
+ ASSERT_EQ(bluez::FakeBluetoothDeviceClient::kPairedDeviceAddress,
+ devices[idx]->GetAddress());
+
+ bluez::FakeBluetoothDeviceClient::Properties* properties =
+ fake_bluetooth_device_client_->GetProperties(dbus::ObjectPath(
+ bluez::FakeBluetoothDeviceClient::kPairedDevicePath));
+
+ properties->address_type.set_valid(false);
+ EXPECT_EQ(BluetoothDevice::ADDR_TYPE_UNKNOWN, devices[idx]->GetAddressType());
+
+ properties->address_type.set_valid(true);
+
+ properties->address_type.ReplaceValue(bluetooth_device::kAddressTypePublic);
+ EXPECT_EQ(BluetoothDevice::ADDR_TYPE_PUBLIC, devices[idx]->GetAddressType());
+
+ properties->address_type.ReplaceValue(bluetooth_device::kAddressTypeRandom);
+ EXPECT_EQ(BluetoothDevice::ADDR_TYPE_RANDOM, devices[idx]->GetAddressType());
+}
+
TEST_F(BluetoothBlueZTest, DeviceClassChanged) {
// Simulate a change of class of a device, as sometimes occurs
// during discovery.
diff --git a/chromium/device/bluetooth/bluez/bluetooth_device_bluez.cc b/chromium/device/bluetooth/bluez/bluetooth_device_bluez.cc
index 0bf76e693c2..1cf95efcd91 100644
--- a/chromium/device/bluetooth/bluez/bluetooth_device_bluez.cc
+++ b/chromium/device/bluetooth/bluez/bluetooth_device_bluez.cc
@@ -302,6 +302,24 @@ std::string BluetoothDeviceBlueZ::GetAddress() const {
return device::CanonicalizeBluetoothAddress(properties->address.value());
}
+BluetoothDeviceBlueZ::AddressType BluetoothDeviceBlueZ::GetAddressType() const {
+ bluez::BluetoothDeviceClient::Properties* properties =
+ bluez::BluezDBusManager::Get()->GetBluetoothDeviceClient()->GetProperties(
+ object_path_);
+ DCHECK(properties);
+
+ if (!properties->address_type.is_valid())
+ return ADDR_TYPE_UNKNOWN;
+
+ if (properties->address_type.value() == bluetooth_device::kAddressTypePublic)
+ return ADDR_TYPE_PUBLIC;
+ if (properties->address_type.value() == bluetooth_device::kAddressTypeRandom)
+ return ADDR_TYPE_RANDOM;
+
+ LOG(WARNING) << "Unknown address type: " << properties->address_type.value();
+ return ADDR_TYPE_UNKNOWN;
+}
+
BluetoothDevice::VendorIDSource BluetoothDeviceBlueZ::GetVendorIDSource()
const {
VendorIDSource vendor_id_source = VENDOR_ID_UNKNOWN;
diff --git a/chromium/device/bluetooth/bluez/bluetooth_device_bluez.h b/chromium/device/bluetooth/bluez/bluetooth_device_bluez.h
index a0fe2f2ac04..500cb5d7c67 100644
--- a/chromium/device/bluetooth/bluez/bluetooth_device_bluez.h
+++ b/chromium/device/bluetooth/bluez/bluetooth_device_bluez.h
@@ -56,6 +56,7 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDeviceBlueZ
uint32_t GetBluetoothClass() const override;
device::BluetoothTransport GetType() const override;
std::string GetAddress() const override;
+ AddressType GetAddressType() const override;
VendorIDSource GetVendorIDSource() const override;
uint16_t GetVendorID() const override;
uint16_t GetProductID() const override;
diff --git a/chromium/device/bluetooth/cast/bluetooth_device_cast.cc b/chromium/device/bluetooth/cast/bluetooth_device_cast.cc
index fc92253f050..58f816b8c5f 100644
--- a/chromium/device/bluetooth/cast/bluetooth_device_cast.cc
+++ b/chromium/device/bluetooth/cast/bluetooth_device_cast.cc
@@ -84,6 +84,11 @@ std::string BluetoothDeviceCast::GetAddress() const {
return address_;
}
+BluetoothDevice::AddressType BluetoothDeviceCast::GetAddressType() const {
+ NOTIMPLEMENTED();
+ return ADDR_TYPE_UNKNOWN;
+}
+
BluetoothDevice::VendorIDSource BluetoothDeviceCast::GetVendorIDSource() const {
return VENDOR_ID_UNKNOWN;
}
@@ -356,11 +361,15 @@ void BluetoothDeviceCast::DisconnectGatt() {
// The device is intentionally not disconnected.
}
-void BluetoothDeviceCast::OnConnect(bool success) {
+void BluetoothDeviceCast::OnConnect(
+ chromecast::bluetooth::RemoteDevice::ConnectStatus status) {
+ bool success =
+ (status == chromecast::bluetooth::RemoteDevice::ConnectStatus::kSuccess);
DVLOG(2) << __func__ << " success:" << success;
pending_connect_ = false;
- if (!success)
+ if (!success) {
DidFailToConnectGatt(ERROR_FAILED);
+ }
}
} // namespace device
diff --git a/chromium/device/bluetooth/cast/bluetooth_device_cast.h b/chromium/device/bluetooth/cast/bluetooth_device_cast.h
index df4bfdddd96..479a81dec00 100644
--- a/chromium/device/bluetooth/cast/bluetooth_device_cast.h
+++ b/chromium/device/bluetooth/cast/bluetooth_device_cast.h
@@ -40,6 +40,7 @@ class BluetoothDeviceCast : public BluetoothDevice {
uint32_t GetBluetoothClass() const override;
BluetoothTransport GetType() const override;
std::string GetAddress() const override;
+ AddressType GetAddressType() const override;
VendorIDSource GetVendorIDSource() const override;
uint16_t GetVendorID() const override;
uint16_t GetProductID() const override;
@@ -120,7 +121,7 @@ class BluetoothDeviceCast : public BluetoothDevice {
void DisconnectGatt() override;
// Called back from connect requests generated from CreateGattConnectionImpl.
- void OnConnect(bool success);
+ void OnConnect(chromecast::bluetooth::RemoteDevice::ConnectStatus status);
// Called in response to GetServices
void OnGetServices(
diff --git a/chromium/device/bluetooth/dbus/bluetooth_device_client.cc b/chromium/device/bluetooth/dbus/bluetooth_device_client.cc
index 2f71e14259a..ae2f4f74408 100644
--- a/chromium/device/bluetooth/dbus/bluetooth_device_client.cc
+++ b/chromium/device/bluetooth/dbus/bluetooth_device_client.cc
@@ -185,6 +185,7 @@ BluetoothDeviceClient::Properties::Properties(
const PropertyChangedCallback& callback)
: dbus::PropertySet(object_proxy, interface_name, callback) {
RegisterProperty(bluetooth_device::kAddressProperty, &address);
+ RegisterProperty(bluetooth_device::kAddressTypeProperty, &address_type);
RegisterProperty(bluetooth_device::kNameProperty, &name);
RegisterProperty(bluetooth_device::kIconProperty, &icon);
RegisterProperty(bluetooth_device::kClassProperty, &bluetooth_class);
@@ -638,6 +639,7 @@ class BluetoothDeviceClientImpl : public BluetoothDeviceClient,
dbus::MessageReader reader(response);
if (!ReadRecordsFromMessage(&reader, &records)) {
std::move(callback).Run(ServiceRecordList());
+ return;
}
std::move(callback).Run(records);
diff --git a/chromium/device/bluetooth/dbus/bluetooth_device_client.h b/chromium/device/bluetooth/dbus/bluetooth_device_client.h
index a6339b17c51..866fdf100bc 100644
--- a/chromium/device/bluetooth/dbus/bluetooth_device_client.h
+++ b/chromium/device/bluetooth/dbus/bluetooth_device_client.h
@@ -51,6 +51,9 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDeviceClient : public BluezDBusClient {
// The Bluetooth device address of the device. Read-only.
dbus::Property<std::string> address;
+ // The Bluetooth address type of the device. Read-only.
+ dbus::Property<std::string> address_type;
+
// The Bluetooth friendly name of the device. Read-only, to give a
// different local name, use the |alias| property.
dbus::Property<std::string> name;
diff --git a/chromium/device/bluetooth/dbus/bluetooth_le_advertisement_service_provider.cc b/chromium/device/bluetooth/dbus/bluetooth_le_advertisement_service_provider.cc
index ce85f4347d1..b36b4f16de4 100644
--- a/chromium/device/bluetooth/dbus/bluetooth_le_advertisement_service_provider.cc
+++ b/chromium/device/bluetooth/dbus/bluetooth_le_advertisement_service_provider.cc
@@ -172,6 +172,7 @@ class BluetoothAdvertisementServiceProviderImpl
method_call, kErrorInvalidArgs,
"No such property: '" + property_name + "'.");
std::move(response_sender).Run(std::move(error_response));
+ return;
}
writer.CloseContainer(&variant_writer);
diff --git a/chromium/device/bluetooth/device.cc b/chromium/device/bluetooth/device.cc
index fd3df58d5de..c9ce478ad91 100644
--- a/chromium/device/bluetooth/device.cc
+++ b/chromium/device/bluetooth/device.cc
@@ -45,6 +45,9 @@ mojom::DeviceInfoPtr Device::ConstructDeviceInfoStruct(
device_info->rssi->value = device->GetInquiryRSSI().value();
}
+ for (auto const& it : device->GetServiceData())
+ device_info->service_data_map.insert_or_assign(it.first, it.second);
+
return device_info;
}
diff --git a/chromium/device/bluetooth/public/mojom/adapter.mojom b/chromium/device/bluetooth/public/mojom/adapter.mojom
index c21d2901aff..5d7fc04a77a 100644
--- a/chromium/device/bluetooth/public/mojom/adapter.mojom
+++ b/chromium/device/bluetooth/public/mojom/adapter.mojom
@@ -49,6 +49,22 @@ struct AdapterInfo {
bool discovering;
};
+// Represents an ongoing BLE advertisement. Releasing it will release the
+// underlying object and stop the advertisement, but callers should prefer to
+// let a call to Unregister() to finish first.
+// Note: Methods which are declared [Sync] are for use by
+// //chrome/services/sharing/nearby; all other usage of their synchronous
+// signatures is strongly discouraged.
+interface Advertisement {
+ // Use to gracefully stop advertising before destroying the message pipe. The
+ // reply callback can be used to synchronize an attempt to re-register an
+ // advertisement; attempting to register an advertisement without first
+ // releasing limited advertisement slots in the hardware may fail with a
+ // busy error.
+ [Sync]
+ Unregister() => ();
+};
+
// Represents a request to discover nearby devices.
// Note: Methods which are declared [Sync] are for use by
// //chrome/services/sharing/nearby; all other usage of their synchronous
@@ -124,9 +140,25 @@ interface Adapter {
[Sync]
GetInfo() => (AdapterInfo info);
- // Sets the client that listens for the adapter's events.
+ // Adds an observer that listens for the adapter's events.
+ [Sync]
+ AddObserver(pending_remote<AdapterObserver> observer) => ();
+
+ // Requests the adapter to broadcast a BLE advertisement on |service_id| with
+ // the associated packet |service_data|. Returns null if advertisement is not
+ // registered successfully.
+ // Important notes:
+ // * This method registers a "non-connectable" advertisement. Any future
+ // effort to allow this API to support "connectable" advertisements would
+ // also require the addition of an API to support hosting a GATT server.
+ // * Bluetooth chips generally can only broadcast a few advertisements,
+ // sometimes even only one, simultaneously. This can be mitigated by
+ // operating systems "rotating" advertisements in the higher software layer,
+ // as Chrome OS does. Non-Chrome OS clients of this API are responsible for
+ // understanding their host OS's and/or hardware's limitations.
[Sync]
- SetClient(pending_remote<AdapterClient> client) => ();
+ RegisterAdvertisement(UUID service_id, array<uint8> service_data) =>
+ (pending_remote<Advertisement>? advertisement);
// Requests the local device to make itself discoverable to nearby remote
// devices.
@@ -164,7 +196,8 @@ interface Adapter {
=> (pending_remote<ServerSocket>? server_socket);
};
-interface AdapterClient {
+// Listener on Bluetooth events. Register as an observer via AddObserver().
+interface AdapterObserver {
// Called when the presence of the adapter changes.
PresentChanged(bool present);
diff --git a/chromium/device/bluetooth/public/mojom/device.mojom b/chromium/device/bluetooth/public/mojom/device.mojom
index a5b89173c37..dcf5b3a18ef 100644
--- a/chromium/device/bluetooth/public/mojom/device.mojom
+++ b/chromium/device/bluetooth/public/mojom/device.mojom
@@ -6,6 +6,13 @@ module bluetooth.mojom;
import "device/bluetooth/public/mojom/uuid.mojom";
+// Important note: the byte arrays which can be accessed from this interface
+// (including "service data", "characteristics", and "descriptors") are
+// arbitrary binary blobs of data provided by a likely untrustworthy device.
+// Clients are responsible for safely parsing this information; please see
+// "The Rule of 2" (//docs/security/rule-of-2.md). C++ clients must parse these
+// blobs in a sandboxed process.
+
// Values representing the possible properties of a characteristic, which
// define how the characteristic can be used. Each of these properties serve
// a role as defined in the Bluetooth Specification.
@@ -60,6 +67,10 @@ struct DeviceInfo {
string address;
bool is_gatt_connected;
RSSIWrapper? rssi;
+
+ // Important: the blobs associated with each UUID are arbitrary and untrusted.
+ // Please refer to the note on "The Rule of 2" at the top of this file.
+ map<UUID, array<uint8>> service_data_map;
};
struct ServiceInfo {
@@ -72,12 +83,18 @@ struct CharacteristicInfo {
string id;
UUID uuid;
uint32 properties;
+
+ // Important: this blob is arbitrary and untrusted. Please refer to the note
+ // on "The Rule of 2" at the top of this file.
array<uint8> last_known_value;
};
struct DescriptorInfo {
string id;
UUID uuid;
+
+ // Important: this blob is arbitrary and untrusted. Please refer to the note
+ // on "The Rule of 2" at the top of this file.
array<uint8> last_known_value;
};
@@ -120,12 +137,16 @@ interface Device {
// Reads the value for the GATT Descriptor with |descriptor_id| in the GATT
// Characteristic with |characteristic_id| in the GATT Service with
// |service_id|.
+ // Important: the returned |value| blob is arbitrary and untrusted. Please
+ // refer to the note on "The Rule of 2" at the top of this file.
ReadValueForDescriptor(string service_id, string characteristic_id,
string descriptor_id) => (GattResult result, array<uint8>? value);
// Writes the |value| for the GATT Descriptor with |descriptor_id| in the GATT
// Characteristic with |characteristic_id| in the GATT Service with
// |service_id|.
+ // Important: the returned |value| blob is arbitrary and untrusted. Please
+ // refer to the note on "The Rule of 2" at the top of this file.
WriteValueForDescriptor(string service_id, string characteristic_id,
string descriptor_id, array<uint8> value) => (GattResult result);
};
diff --git a/chromium/device/fido/BUILD.gn b/chromium/device/fido/BUILD.gn
index a5a5d64b764..ec3c52825ac 100644
--- a/chromium/device/fido/BUILD.gn
+++ b/chromium/device/fido/BUILD.gn
@@ -16,8 +16,11 @@ component("fido") {
"cable/cable_discovery_data.h",
"cable/noise.cc",
"cable/noise.h",
+ "cable/v2_constants.h",
"cable/v2_handshake.cc",
"cable/v2_handshake.h",
+ "cable/websocket_adapter.cc",
+ "cable/websocket_adapter.h",
"cbor_extract.cc",
"ed25519_public_key.cc",
"ed25519_public_key.h",
@@ -113,8 +116,8 @@ component("fido") {
"cable/fido_cable_handshake_handler.h",
"cable/fido_tunnel_device.cc",
"cable/fido_tunnel_device.h",
- "cable/websocket_adapter.cc",
- "cable/websocket_adapter.h",
+ "cable/v2_discovery.cc",
+ "cable/v2_discovery.h",
"client_data.cc",
"client_data.h",
"credential_management.cc",
@@ -160,6 +163,8 @@ component("fido") {
"hid/fido_hid_message.h",
"hid/fido_hid_packet.cc",
"hid/fido_hid_packet.h",
+ "large_blob.cc",
+ "large_blob.h",
"make_credential_request_handler.cc",
"make_credential_request_handler.h",
"make_credential_task.cc",
@@ -261,6 +266,50 @@ component("fido") {
}
}
+static_library("cablev2_registration") {
+ sources = [
+ "cable/v2_registration.cc",
+ "cable/v2_registration.h",
+ ]
+ deps = [
+ ":fido",
+ "//base",
+ "//components/cbor",
+ "//components/device_event_log",
+ "//components/gcm_driver",
+ "//components/gcm_driver/instance_id",
+ ]
+}
+
+static_library("cablev2_authenticator") {
+ sources = [
+ "cable/v2_authenticator.cc",
+ "cable/v2_authenticator.h",
+ ]
+ deps = [
+ ":fido",
+ "//components/cbor",
+ "//components/device_event_log",
+ "//services/network/public/mojom",
+ ]
+}
+
+static_library("cablev2_test_util") {
+ testonly = true
+ sources = [
+ "cable/v2_test_util.cc",
+ "cable/v2_test_util.h",
+ ]
+ deps = [
+ ":cablev2_authenticator",
+ ":fido",
+ "//components/cbor",
+ "//crypto",
+ "//services/network:test_support",
+ "//services/network/public/mojom",
+ ]
+}
+
if (is_chromeos) {
proto_library("u2f_proto") {
sources = [ "//third_party/cros_system_api/dbus/u2f/u2f_interface.proto" ]
diff --git a/chromium/device/fido/DEPS b/chromium/device/fido/DEPS
index 690e920335a..abccfd8760a 100644
--- a/chromium/device/fido/DEPS
+++ b/chromium/device/fido/DEPS
@@ -1,10 +1,12 @@
include_rules = [
"+components/apdu",
"+components/cbor",
+ "+components/gcm_driver",
"+crypto",
"+dbus",
"+net/base",
"+net/cert",
+ "+net/cookies",
"+net/traffic_annotation",
"+services/network",
"+third_party/boringssl/src/include",
diff --git a/chromium/device/fido/aoa/android_accessory_discovery.cc b/chromium/device/fido/aoa/android_accessory_discovery.cc
index fec676d6941..d55fcd4154d 100644
--- a/chromium/device/fido/aoa/android_accessory_discovery.cc
+++ b/chromium/device/fido/aoa/android_accessory_discovery.cc
@@ -183,6 +183,7 @@ void AndroidAccessoryDiscovery::OnOpenAccessory(
base::BindOnce(&AndroidAccessoryDiscovery::OnAccessoryConfigured,
weak_factory_.GetWeakPtr(), std::move(device),
interface_info));
+ return;
}
OnAccessoryConfigured(std::move(device), interface_info, /*success=*/true);
diff --git a/chromium/device/fido/authenticator_get_assertion_response.cc b/chromium/device/fido/authenticator_get_assertion_response.cc
index f1c7b3ed829..c012d29acdd 100644
--- a/chromium/device/fido/authenticator_get_assertion_response.cc
+++ b/chromium/device/fido/authenticator_get_assertion_response.cc
@@ -97,6 +97,11 @@ AuthenticatorGetAssertionResponse::SetNumCredentials(uint8_t num_credentials) {
return *this;
}
+void AuthenticatorGetAssertionResponse::set_large_blob_key(
+ const base::span<const uint8_t, kLargeBlobKeyLength> large_blob_key) {
+ large_blob_key_ = fido_parsing_utils::Materialize(large_blob_key);
+}
+
base::Optional<base::span<const uint8_t>>
AuthenticatorGetAssertionResponse::hmac_secret() const {
if (hmac_secret_) {
diff --git a/chromium/device/fido/authenticator_get_assertion_response.h b/chromium/device/fido/authenticator_get_assertion_response.h
index 9cb3740c59d..1e2ddd7c1ba 100644
--- a/chromium/device/fido/authenticator_get_assertion_response.h
+++ b/chromium/device/fido/authenticator_get_assertion_response.h
@@ -67,6 +67,13 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorGetAssertionResponse
android_client_data_ext_ = data;
}
+ base::Optional<std::array<uint8_t, kLargeBlobKeyLength>> large_blob_key()
+ const {
+ return large_blob_key_;
+ }
+ void set_large_blob_key(
+ const base::span<const uint8_t, kLargeBlobKeyLength> large_blob_key);
+
// hmac_secret contains the output of the hmac_secret extension.
base::Optional<base::span<const uint8_t>> hmac_secret() const;
void set_hmac_secret(std::vector<uint8_t>);
@@ -92,6 +99,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorGetAssertionResponse
// authenticator output.
base::Optional<std::vector<uint8_t>> android_client_data_ext_;
+ // The large blob key associated to the credential. This value is only
+ // returned if the assertion request contains the largeBlobKey extension on a
+ // capable authenticator and the credential has an associated large blob key.
+ base::Optional<std::array<uint8_t, kLargeBlobKeyLength>> large_blob_key_;
+
DISALLOW_COPY_AND_ASSIGN(AuthenticatorGetAssertionResponse);
};
diff --git a/chromium/device/fido/authenticator_get_info_response.cc b/chromium/device/fido/authenticator_get_info_response.cc
index fa5bd5efda1..ad0303ecf94 100644
--- a/chromium/device/fido/authenticator_get_info_response.cc
+++ b/chromium/device/fido/authenticator_get_info_response.cc
@@ -59,8 +59,6 @@ std::vector<uint8_t> AuthenticatorGetInfoResponse::EncodeToCBOR(
case Ctap2Version::kCtap2_1:
version_array.emplace_back(kCtap2_1Version);
break;
- case Ctap2Version::kUnknown:
- NOTREACHED();
}
}
break;
diff --git a/chromium/device/fido/authenticator_make_credential_response.cc b/chromium/device/fido/authenticator_make_credential_response.cc
index bd3ec120a86..fc6399109f5 100644
--- a/chromium/device/fido/authenticator_make_credential_response.cc
+++ b/chromium/device/fido/authenticator_make_credential_response.cc
@@ -55,9 +55,11 @@ AuthenticatorMakeCredentialResponse::CreateFromU2fRegisterResponse(
if (!fido_attestation_statement)
return base::nullopt;
- return AuthenticatorMakeCredentialResponse(
+ AuthenticatorMakeCredentialResponse response(
transport_used, AttestationObject(std::move(authenticator_data),
std::move(fido_attestation_statement)));
+ response.is_resident_key = false;
+ return response;
}
AuthenticatorMakeCredentialResponse::AuthenticatorMakeCredentialResponse(
@@ -70,8 +72,9 @@ AuthenticatorMakeCredentialResponse::AuthenticatorMakeCredentialResponse(
AuthenticatorMakeCredentialResponse::AuthenticatorMakeCredentialResponse(
AuthenticatorMakeCredentialResponse&& that) = default;
-AuthenticatorMakeCredentialResponse& AuthenticatorMakeCredentialResponse::
-operator=(AuthenticatorMakeCredentialResponse&& other) = default;
+AuthenticatorMakeCredentialResponse&
+AuthenticatorMakeCredentialResponse::operator=(
+ AuthenticatorMakeCredentialResponse&& other) = default;
AuthenticatorMakeCredentialResponse::~AuthenticatorMakeCredentialResponse() =
default;
@@ -102,6 +105,11 @@ AuthenticatorMakeCredentialResponse::GetRpIdHash() const {
return attestation_object_.rp_id_hash();
}
+void AuthenticatorMakeCredentialResponse::set_large_blob_key(
+ const base::span<const uint8_t, kLargeBlobKeyLength> large_blob_key) {
+ large_blob_key_ = fido_parsing_utils::Materialize(large_blob_key);
+}
+
std::vector<uint8_t> AsCTAPStyleCBORBytes(
const AuthenticatorMakeCredentialResponse& response) {
const AttestationObject& object = response.attestation_object();
@@ -116,6 +124,9 @@ std::vector<uint8_t> AsCTAPStyleCBORBytes(
if (response.enterprise_attestation_returned) {
map.emplace(4, true);
}
+ if (response.large_blob_key()) {
+ map.emplace(5, cbor::Value(*response.large_blob_key()));
+ }
auto encoded_bytes = cbor::Writer::Write(cbor::Value(std::move(map)));
DCHECK(encoded_bytes);
return std::move(*encoded_bytes);
diff --git a/chromium/device/fido/authenticator_make_credential_response.h b/chromium/device/fido/authenticator_make_credential_response.h
index 62fe331cc08..86fd2130632 100644
--- a/chromium/device/fido/authenticator_make_credential_response.h
+++ b/chromium/device/fido/authenticator_make_credential_response.h
@@ -78,12 +78,24 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorMakeCredentialResponse
android_client_data_ext_ = data;
}
+ base::Optional<std::array<uint8_t, kLargeBlobKeyLength>> large_blob_key()
+ const {
+ return large_blob_key_;
+ }
+ void set_large_blob_key(
+ const base::span<const uint8_t, kLargeBlobKeyLength> large_blob_key);
+
// enterprise_attestation_returned is true if the authenticator indicated that
// it returned an enterprise attestation. Note: U2F authenticators can
// support enterprise/individual attestation but cannot indicate when they
// have done so, so this will always be false in the U2F case.
bool enterprise_attestation_returned = false;
+ // is_resident_key indicates whether the created credential is client-side
+ // discoverable. It is nullopt if no discoverable credential was requested,
+ // but the authenticator may have created one anyway.
+ base::Optional<bool> is_resident_key;
+
private:
AttestationObject attestation_object_;
@@ -95,6 +107,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorMakeCredentialResponse
// authenticator output.
base::Optional<std::vector<uint8_t>> android_client_data_ext_;
+ // The large blob key associated to the credential. This value is only
+ // returned if the credential is created with the largeBlobKey extension on a
+ // capable authenticator.
+ base::Optional<std::array<uint8_t, kLargeBlobKeyLength>> large_blob_key_;
+
DISALLOW_COPY_AND_ASSIGN(AuthenticatorMakeCredentialResponse);
};
diff --git a/chromium/device/fido/authenticator_selection_criteria.cc b/chromium/device/fido/authenticator_selection_criteria.cc
index 50b892adc80..aa23d1cc56a 100644
--- a/chromium/device/fido/authenticator_selection_criteria.cc
+++ b/chromium/device/fido/authenticator_selection_criteria.cc
@@ -10,10 +10,10 @@ AuthenticatorSelectionCriteria::AuthenticatorSelectionCriteria() = default;
AuthenticatorSelectionCriteria::AuthenticatorSelectionCriteria(
AuthenticatorAttachment authenticator_attachment,
- bool require_resident_key,
+ ResidentKeyRequirement resident_key,
UserVerificationRequirement user_verification_requirement)
: authenticator_attachment_(authenticator_attachment),
- require_resident_key_(require_resident_key),
+ resident_key_(resident_key),
user_verification_requirement_(user_verification_requirement) {}
AuthenticatorSelectionCriteria::AuthenticatorSelectionCriteria(
@@ -31,7 +31,7 @@ AuthenticatorSelectionCriteria& AuthenticatorSelectionCriteria::operator=(
bool AuthenticatorSelectionCriteria::operator==(
const AuthenticatorSelectionCriteria& other) const {
return authenticator_attachment_ == other.authenticator_attachment_ &&
- require_resident_key_ == other.require_resident_key_ &&
+ resident_key_ == other.resident_key_ &&
user_verification_requirement_ == other.user_verification_requirement_;
}
diff --git a/chromium/device/fido/authenticator_selection_criteria.h b/chromium/device/fido/authenticator_selection_criteria.h
index 69cb8d7a255..d809eb3e4cb 100644
--- a/chromium/device/fido/authenticator_selection_criteria.h
+++ b/chromium/device/fido/authenticator_selection_criteria.h
@@ -7,18 +7,20 @@
#include "base/component_export.h"
#include "device/fido/fido_constants.h"
+#include "device/fido/fido_types.h"
namespace device {
// Represents authenticator properties the relying party can specify to restrict
// the type of authenticator used in creating credentials.
+//
// https://w3c.github.io/webauthn/#authenticatorSelection
class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorSelectionCriteria {
public:
AuthenticatorSelectionCriteria();
AuthenticatorSelectionCriteria(
AuthenticatorAttachment authenticator_attachment,
- bool require_resident_key,
+ ResidentKeyRequirement resident_key,
UserVerificationRequirement user_verification_requirement);
AuthenticatorSelectionCriteria(const AuthenticatorSelectionCriteria& other);
AuthenticatorSelectionCriteria(AuthenticatorSelectionCriteria&& other);
@@ -33,7 +35,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorSelectionCriteria {
return authenticator_attachment_;
}
- bool require_resident_key() const { return require_resident_key_; }
+ ResidentKeyRequirement resident_key() const { return resident_key_; }
UserVerificationRequirement user_verification_requirement() const {
return user_verification_requirement_;
@@ -43,8 +45,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorSelectionCriteria {
AuthenticatorAttachment attachment) {
authenticator_attachment_ = attachment;
}
- void SetRequireResidentKeyForTesting(bool require) {
- require_resident_key_ = require;
+ void SetResidentKeyForTesting(ResidentKeyRequirement resident_key) {
+ resident_key_ = resident_key;
}
void SetUserVerificationRequirementForTesting(
UserVerificationRequirement uv) {
@@ -54,7 +56,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorSelectionCriteria {
private:
AuthenticatorAttachment authenticator_attachment_ =
AuthenticatorAttachment::kAny;
- bool require_resident_key_ = false;
+ ResidentKeyRequirement resident_key_ = ResidentKeyRequirement::kDiscouraged;
UserVerificationRequirement user_verification_requirement_ =
UserVerificationRequirement::kPreferred;
};
diff --git a/chromium/device/fido/authenticator_supported_options.cc b/chromium/device/fido/authenticator_supported_options.cc
index 7b9951d496d..f0d49481641 100644
--- a/chromium/device/fido/authenticator_supported_options.cc
+++ b/chromium/device/fido/authenticator_supported_options.cc
@@ -96,6 +96,10 @@ cbor::Value AsCBOR(const AuthenticatorSupportedOptions& options) {
option_map.emplace(kEnterpriseAttestationKey, true);
}
+ if (options.supports_large_blobs) {
+ option_map.emplace(kLargeBlobsKey, true);
+ }
+
return cbor::Value(std::move(option_map));
}
diff --git a/chromium/device/fido/authenticator_supported_options.h b/chromium/device/fido/authenticator_supported_options.h
index c93357a6cf5..2b0e115d71a 100644
--- a/chromium/device/fido/authenticator_supported_options.h
+++ b/chromium/device/fido/authenticator_supported_options.h
@@ -97,6 +97,9 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) AuthenticatorSupportedOptions {
// uninteresting to Chromium because we do not support the administrative
// operation to configure it. Thus this member reduces to a boolean.)
bool enterprise_attestation = false;
+ // Indicates whether the authenticator supports the authenticatorLargeBlobs
+ // command.
+ bool supports_large_blobs = false;
};
COMPONENT_EXPORT(DEVICE_FIDO)
diff --git a/chromium/device/fido/bio/enrollment.h b/chromium/device/fido/bio/enrollment.h
index 92ff22a0831..77ee7dc4b77 100644
--- a/chromium/device/fido/bio/enrollment.h
+++ b/chromium/device/fido/bio/enrollment.h
@@ -145,7 +145,7 @@ struct BioEnrollmentRequest {
~BioEnrollmentRequest();
private:
- BioEnrollmentRequest(Version);
+ explicit BioEnrollmentRequest(Version);
};
struct COMPONENT_EXPORT(DEVICE_FIDO) BioEnrollmentResponse {
diff --git a/chromium/device/fido/cable/cable_discovery_data.cc b/chromium/device/fido/cable/cable_discovery_data.cc
index 005b551114b..71dffdc4349 100644
--- a/chromium/device/fido/cable/cable_discovery_data.cc
+++ b/chromium/device/fido/cable/cable_discovery_data.cc
@@ -7,6 +7,7 @@
#include <cstring>
#include "base/time/time.h"
+#include "components/cbor/values.h"
#include "crypto/random.h"
#include "device/fido/cable/v2_handshake.h"
#include "device/fido/fido_parsing_utils.h"
@@ -19,29 +20,6 @@
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(
@@ -57,39 +35,6 @@ CableDiscoveryData::CableDiscoveryData(
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;
@@ -109,12 +54,6 @@ bool CableDiscoveryData::operator==(const CableDiscoveryData& other) const {
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;
@@ -126,107 +65,59 @@ bool CableDiscoveryData::MatchV1(const CableEidArray& eid) const {
return eid == v1->authenticator_eid;
}
-bool CableDiscoveryData::MatchV2(const CableEidArray& eid,
- CableEidArray* out_eid) const {
- DCHECK_EQ(version, Version::V2);
-
- // Attempt to decrypt the EID with the EID generator key and check whether
- // it has a valid structure.
- AES_KEY key;
- CableEidArray& out = *out_eid;
- 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");
- AES_decrypt(/*in=*/eid.data(), /*out=*/out.data(), &key);
- return cablev2::eid::IsValid(out);
-}
+namespace cablev2 {
-// static
-QRGeneratorKey CableDiscoveryData::NewQRKey() {
- QRGeneratorKey key;
- crypto::RandBytes(key.data(), key.size());
- return key;
-}
+Pairing::Pairing() = default;
+Pairing::~Pairing() = default;
// static
-int64_t CableDiscoveryData::CurrentTimeTick() {
- // The ticks are currently 256ms.
- return base::TimeTicks::Now().since_origin().InMilliseconds() >> 8;
-}
+base::Optional<std::unique_ptr<Pairing>> Pairing::Parse(
+ const cbor::Value& cbor,
+ uint32_t tunnel_server_domain,
+ base::span<const uint8_t, kQRSeedSize> local_identity_seed,
+ base::span<const uint8_t, 32> handshake_hash) {
+ if (!cbor.is_map()) {
+ return base::nullopt;
+ }
-// 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;
-}
+ const cbor::Value::MapValue& map = cbor.GetMap();
+ auto pairing = std::make_unique<Pairing>();
+
+ const std::array<cbor::Value::MapValue::const_iterator, 5> its = {
+ map.find(cbor::Value(1)), map.find(cbor::Value(2)),
+ map.find(cbor::Value(3)), map.find(cbor::Value(4)),
+ map.find(cbor::Value(6))};
+ const cbor::Value::MapValue::const_iterator name_it =
+ map.find(cbor::Value(5));
+ if (name_it == map.end() || !name_it->second.is_string() ||
+ std::any_of(
+ &its[0], &its[its.size()],
+ [&map](const cbor::Value::MapValue::const_iterator& it) -> bool {
+ return it == map.end() || !it->second.is_bytestring();
+ }) ||
+ its[3]->second.GetBytestring().size() !=
+ std::tuple_size<decltype(pairing->peer_public_key_x962)>::value) {
+ }
-// 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;
-}
+ pairing->tunnel_server_domain =
+ tunnelserver::DecodeDomain(tunnel_server_domain),
+ pairing->contact_id = its[0]->second.GetBytestring();
+ pairing->id = its[1]->second.GetBytestring();
+ pairing->secret = its[2]->second.GetBytestring();
+ const std::vector<uint8_t>& peer_public_key = its[3]->second.GetBytestring();
+ std::copy(peer_public_key.begin(), peer_public_key.end(),
+ pairing->peer_public_key_x962.begin());
+ pairing->name = name_it->second.GetString();
+
+ if (!VerifyPairingSignature(local_identity_seed,
+ pairing->peer_public_key_x962, handshake_hash,
+ its[4]->second.GetBytestring())) {
+ return base::nullopt;
+ }
-// 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;
+ return pairing;
}
-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);
-
- static const char kTunnelIDGen[] = "caBLE QR to tunnel ID generator key";
- ok = HKDF(v2->tunnel_id_gen_key.data(), v2->tunnel_id_gen_key.size(),
- EVP_sha256(), qr_secret.data(), qr_secret.size(), /*salt=*/nullptr,
- 0, reinterpret_cast<const uint8_t*>(kTunnelIDGen),
- sizeof(kTunnelIDGen) - 1);
- DCHECK(ok);
-}
+} // namespace cablev2
} // namespace device
diff --git a/chromium/device/fido/cable/cable_discovery_data.h b/chromium/device/fido/cable/cable_discovery_data.h
index 54bde8a217a..14c9dfd617a 100644
--- a/chromium/device/fido/cable/cable_discovery_data.h
+++ b/chromium/device/fido/cable/cable_discovery_data.h
@@ -10,26 +10,22 @@
#include "base/component_export.h"
#include "base/containers/span.h"
+#include "base/optional.h"
+#include "device/fido/cable/v2_constants.h"
#include "device/fido/fido_constants.h"
+namespace cbor {
+class Value;
+}
+
namespace device {
constexpr size_t kCableEphemeralIdSize = 16;
constexpr size_t kCableSessionPreKeySize = 32;
-constexpr size_t kCableQRSecretSize = 16;
constexpr size_t kCableNonceSize = 8;
-constexpr size_t kCableIdentityKeySeedSize = 32;
-constexpr size_t kCableCompressedPublicKeySize =
- /* type byte */ 1 + /* field element */ (256 / 8);
-constexpr size_t kCableQRDataSize =
- kCableCompressedPublicKeySize + kCableQRSecretSize;
using CableEidArray = std::array<uint8_t, kCableEphemeralIdSize>;
using CableSessionPreKeyArray = std::array<uint8_t, kCableSessionPreKeySize>;
-// QRGeneratorKey is a random, AES-256 key that is used by
-// |CableDiscoveryData::DeriveQRKeyMaterial| to encrypt a coarse timestamp and
-// generate QR secrets, EIDs, etc.
-using QRGeneratorKey = std::array<uint8_t, 32>;
// CableNonce is a nonce used in BLE handshaking.
using CableNonce = std::array<uint8_t, 8>;
// CableEidGeneratorKey is an AES-256 key that is used to encrypt a 64-bit nonce
@@ -42,8 +38,6 @@ using CableTunnelIDGeneratorKey = std::array<uint8_t, 32>;
// CableAuthenticatorIdentityKey is a P-256 public value used to authenticate a
// paired phone.
using CableAuthenticatorIdentityKey = std::array<uint8_t, kP256X962Length>;
-using CableIdentityKeySeed = std::array<uint8_t, kCableIdentityKeySeedSize>;
-using CableQRData = std::array<uint8_t, kCableQRDataSize>;
// Encapsulates information required to discover Cable device per single
// credential. When multiple credentials are enrolled to a single account
@@ -56,32 +50,16 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) CableDiscoveryData {
enum class Version {
INVALID,
V1,
- V2,
};
CableDiscoveryData(Version version,
const CableEidArray& client_eid,
const CableEidArray& authenticator_eid,
const CableSessionPreKeyArray& session_pre_key);
- // Creates discovery data given a specific QR secret and identity key seed.
- // This will be used on the QR-displaying-side of a QR handshake. See
- // |DeriveQRSecret| and |DeriveIdentityKeySeed| for how to generate such
- // secrets.
- CableDiscoveryData(
- base::span<const uint8_t, kCableQRSecretSize> qr_secret,
- base::span<const uint8_t, kCableIdentityKeySeedSize> identity_key_seed);
CableDiscoveryData();
CableDiscoveryData(const CableDiscoveryData& data);
~CableDiscoveryData();
- // Creates discovery data given QR data, which contains a compressed public
- // key and the QR secret. This will be used by the QR-scanning-side of a QR
- // handshake. Returns |nullopt| if the embedded elliptic-curve point is
- // invalid.
- static base::Optional<CableDiscoveryData> FromQRData(
- base::span<const uint8_t,
- kCableCompressedPublicKeySize + kCableQRSecretSize> qr_data);
-
CableDiscoveryData& operator=(const CableDiscoveryData& other);
bool operator==(const CableDiscoveryData& other) const;
@@ -89,38 +67,6 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) CableDiscoveryData {
// instance, which must be version one.
bool MatchV1(const CableEidArray& candidate_eid) const;
- // MatchV2 returns true if |candidate_eid| matches this caBLE discovery
- // instance, which must be version two. If so, |*out_eid| is set to the value
- // of the decrypted EID.
- bool MatchV2(const CableEidArray& candidate_eid,
- CableEidArray* out_eid) const;
-
- // NewQRKey returns a random key for QR generation.
- static QRGeneratorKey NewQRKey();
-
- // CurrentTimeTick returns the current time as used by QR generation. The size
- // of these ticks is a purely local matter for Chromium.
- static int64_t CurrentTimeTick();
-
- // DeriveQRKeyMaterial returns a QR-secret given a generating key and a
- // timestamp.
- static std::array<uint8_t, kCableQRSecretSize> DeriveQRSecret(
- base::span<const uint8_t, 32> qr_generator_key,
- const int64_t tick);
-
- // DeriveIdentityKeySeed returns a seed that can be used to create a P-256
- // identity key for a handshake using |EC_KEY_derive_from_secret|.
- static CableIdentityKeySeed DeriveIdentityKeySeed(
- base::span<const uint8_t, 32> qr_generator_key,
- const int64_t tick);
-
- // DeriveQRData returns the QR data, a combination of QR secret and public
- // identity key. This is base64url-encoded and placed in a caBLE v2 QR code
- // with a prefix prepended.
- static CableQRData DeriveQRData(
- base::span<const uint8_t, 32> qr_generator_key,
- const int64_t tick);
-
// version indicates whether v1 or v2 data is contained in this object.
// |INVALID| is not a valid version but is set as the default to catch any
// cases where the version hasn't been set explicitly.
@@ -132,30 +78,49 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) CableDiscoveryData {
CableSessionPreKeyArray session_pre_key;
};
base::Optional<V1Data> v1;
+};
- struct COMPONENT_EXPORT(DEVICE_FIDO) V2Data {
- V2Data();
- V2Data(const V2Data&);
- ~V2Data();
-
- CableEidGeneratorKey eid_gen_key;
- CablePskGeneratorKey psk_gen_key;
- CableTunnelIDGeneratorKey tunnel_id_gen_key;
- base::Optional<CableAuthenticatorIdentityKey> peer_identity;
- base::Optional<CableIdentityKeySeed> local_identity_seed;
- // peer_name is an authenticator-controlled, UTF8-valid string containing
- // the self-reported, human-friendly name of a v2 authenticator. This need
- // not be filled in when handshaking but an authenticator may provide it
- // when offering long-term pairing data.
- base::Optional<std::string> peer_name;
- };
- base::Optional<V2Data> v2;
-
- private:
- void InitFromQRSecret(
- base::span<const uint8_t, kCableQRSecretSize> qr_secret);
+namespace cablev2 {
+
+// Pairing represents information previously received from a caBLEv2
+// authenticator that enables future interactions to skip scanning a QR code.
+struct COMPONENT_EXPORT(DEVICE_FIDO) Pairing {
+ Pairing();
+ ~Pairing();
+ Pairing(const Pairing&) = delete;
+ Pairing& operator=(const Pairing&) = delete;
+
+ // Parse builds a |Pairing| from an authenticator message. The signature
+ // within the structure is validated by using |local_identity_seed| and
+ // |handshake_hash|.
+ static base::Optional<std::unique_ptr<Pairing>> Parse(
+ const cbor::Value& cbor,
+ uint32_t tunnel_server_domain,
+ base::span<const uint8_t, kQRSeedSize> local_identity_seed,
+ base::span<const uint8_t, 32> handshake_hash);
+
+ // tunnel_server_domain is known to be a valid hostname as it's constructed
+ // from the 22-bit value in the BLE advert rather than being parsed as a
+ // string from the authenticator.
+ std::string tunnel_server_domain;
+ // contact_id is an opaque value that is sent to the tunnel service in order
+ // to identify the caBLEv2 authenticator.
+ std::vector<uint8_t> contact_id;
+ // id is an opaque identifier that is sent via the tunnel service, to the
+ // authenticator, to identify this specific pairing.
+ std::vector<uint8_t> id;
+ // secret is the shared secret that authenticates the desktop to the
+ // authenticator.
+ std::vector<uint8_t> secret;
+ // peer_public_key_x962 is the authenticator's public key.
+ std::array<uint8_t, kP256X962Length> peer_public_key_x962;
+ // name is a human-friendly name for the authenticator, specified by that
+ // authenticator. (For example "Pixel 3".)
+ std::string name;
};
+} // namespace cablev2
+
} // namespace device
#endif // DEVICE_FIDO_CABLE_CABLE_DISCOVERY_DATA_H_
diff --git a/chromium/device/fido/cable/fido_ble_connection_unittest.cc b/chromium/device/fido/cable/fido_ble_connection_unittest.cc
index 3abfca15fb3..a07c6924844 100644
--- a/chromium/device/fido/cable/fido_ble_connection_unittest.cc
+++ b/chromium/device/fido/cable/fido_ble_connection_unittest.cc
@@ -226,8 +226,7 @@ class FidoBleConnectionTest : public ::testing::Test {
EXPECT_CALL(*fido_device_, GetAddress)
.WillRepeatedly(::testing::Return(new_address));
for (auto& observer : adapter_->GetObservers())
- observer.DeviceAddressChanged(adapter_.get(), fido_device_,
- std::move(old_address));
+ observer.DeviceAddressChanged(adapter_.get(), fido_device_, old_address);
}
void SetNextReadControlPointLengthReponse(bool success,
diff --git a/chromium/device/fido/cable/fido_cable_discovery.cc b/chromium/device/fido/cable/fido_cable_discovery.cc
index f4e8c2fb901..d1d4ca3ecc1 100644
--- a/chromium/device/fido/cable/fido_cable_discovery.cc
+++ b/chromium/device/fido/cable/fido_cable_discovery.cc
@@ -126,24 +126,6 @@ enum class FidoCableDiscovery::CableV1DiscoveryEvent : int {
kMaxValue = kScanningStoppedUnexpectedly,
};
-// FidoCableDiscovery::Result -------------------------------------------------
-
-FidoCableDiscovery::Result::Result() = default;
-
-FidoCableDiscovery::Result::Result(
- const CableDiscoveryData& in_discovery_data,
- const CableEidArray& in_eid,
- base::Optional<CableEidArray> in_decrypted_eid,
- base::Optional<int> in_ticks_back)
- : discovery_data(in_discovery_data),
- eid(in_eid),
- decrypted_eid(std::move(in_decrypted_eid)),
- ticks_back(in_ticks_back) {}
-
-FidoCableDiscovery::Result::Result(const Result& other) = default;
-
-FidoCableDiscovery::Result::~Result() = default;
-
// FidoCableDiscovery::ObservedDeviceData -------------------------------------
FidoCableDiscovery::ObservedDeviceData::ObservedDeviceData() = default;
@@ -153,17 +135,11 @@ FidoCableDiscovery::ObservedDeviceData::~ObservedDeviceData() = default;
FidoCableDiscovery::FidoCableDiscovery(
std::vector<CableDiscoveryData> discovery_data,
- base::Optional<QRGeneratorKey> qr_generator_key,
- base::Optional<
- base::RepeatingCallback<void(std::unique_ptr<CableDiscoveryData>)>>
- pairing_callback,
- network::mojom::NetworkContext* network_context)
+ FidoDeviceDiscovery::BLEObserver* ble_observer)
: FidoDeviceDiscovery(
FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy),
discovery_data_(std::move(discovery_data)),
- qr_generator_key_(std::move(qr_generator_key)),
- pairing_callback_(std::move(pairing_callback)),
- network_context_(network_context) {
+ ble_observer_(ble_observer) {
// Windows currently does not support multiple EIDs, thus we ignore any extra
// discovery data.
// TODO(https://crbug.com/837088): Add support for multiple EIDs on Windows.
@@ -212,7 +188,6 @@ FidoCableDiscovery::CreateV1HandshakeHandler(
device, nonce, discovery_data.v1->session_pre_key);
}
- case CableDiscoveryData::Version::V2:
case CableDiscoveryData::Version::INVALID:
CHECK(false);
return nullptr;
@@ -296,8 +271,11 @@ void FidoCableDiscovery::DeviceChanged(BluetoothAdapter* adapter,
void FidoCableDiscovery::DeviceRemoved(BluetoothAdapter* adapter,
BluetoothDevice* device) {
- if (IsCableDevice(device) && GetCableDiscoveryData(device)) {
- const auto& device_address = device->GetAddress();
+ const auto& device_address = device->GetAddress();
+ if (IsCableDevice(device) &&
+ // It only matters if V1 devices are "removed" because V2 devices do not
+ // transport data over BLE.
+ base::Contains(active_devices_, device_address)) {
FIDO_LOG(DEBUG) << "caBLE device removed: " << device_address;
RemoveDevice(FidoCableDevice::GetIdForAddress(device_address));
}
@@ -476,18 +454,21 @@ void FidoCableDiscovery::CableDeviceFound(BluetoothAdapter* adapter,
return;
}
- base::Optional<Result> result = GetCableDiscoveryData(device);
- if (!result || base::Contains(active_authenticator_eids_, result->eid)) {
+ base::Optional<V1DiscoveryDataAndEID> v1_match =
+ GetCableDiscoveryData(device);
+ if (!v1_match) {
return;
}
- FIDO_LOG(EVENT) << "Found new caBLE device.";
- if (result->discovery_data.version == CableDiscoveryData::Version::V1) {
- RecordCableV1DiscoveryEventOnce(
- CableV1DiscoveryEvent::kFirstCableDeviceFound);
+ if (base::Contains(active_authenticator_eids_, v1_match->second)) {
+ return;
}
+ active_authenticator_eids_.insert(v1_match->second);
active_devices_.insert(device_address);
- active_authenticator_eids_.insert(result->eid);
+
+ FIDO_LOG(EVENT) << "Found new caBLEv1 device.";
+ RecordCableV1DiscoveryEventOnce(
+ CableV1DiscoveryEvent::kFirstCableDeviceFound);
#if defined(OS_CHROMEOS) || defined(OS_LINUX)
// Speed up GATT service discovery on ChromeOS/BlueZ.
@@ -501,41 +482,21 @@ void FidoCableDiscovery::CableDeviceFound(BluetoothAdapter* adapter,
}
#endif // defined(OS_CHROMEOS) || defined(OS_LINUX)
- switch (result->discovery_data.version) {
- case CableDiscoveryData::Version::V1: {
- auto cable_device =
- std::make_unique<FidoCableDevice>(adapter, device_address);
- cable_device->set_observer(this);
-
- std::unique_ptr<FidoCableHandshakeHandler> handshake_handler =
- CreateV1HandshakeHandler(cable_device.get(), result->discovery_data,
- result->eid);
- auto* const handshake_handler_ptr = handshake_handler.get();
- active_handshakes_.emplace_back(std::move(cable_device),
- std::move(handshake_handler));
-
- StopAdvertisements(
- base::BindOnce(&FidoCableDiscovery::ConductEncryptionHandshake,
- weak_factory_.GetWeakPtr(), handshake_handler_ptr,
- result->discovery_data.version));
- break;
- }
+ auto cable_device =
+ std::make_unique<FidoCableDevice>(adapter, device_address);
+ cable_device->set_observer(this);
- case CableDiscoveryData::Version::V2: {
- if (!base::FeatureList::IsEnabled(device::kWebAuthPhoneSupport) ||
- !network_context_) {
- return;
- }
- AddDevice(std::make_unique<cablev2::FidoTunnelDevice>(
- network_context_, *result->discovery_data.v2, result->eid,
- *result->decrypted_eid));
- break;
- }
+ std::unique_ptr<FidoCableHandshakeHandler> handshake_handler =
+ CreateV1HandshakeHandler(cable_device.get(), v1_match->first,
+ v1_match->second);
+ auto* const handshake_handler_ptr = handshake_handler.get();
+ active_handshakes_.emplace_back(std::move(cable_device),
+ std::move(handshake_handler));
- case CableDiscoveryData::Version::INVALID:
- CHECK(false);
- return;
- }
+ StopAdvertisements(
+ base::BindOnce(&FidoCableDiscovery::ConductEncryptionHandshake,
+ weak_factory_.GetWeakPtr(), handshake_handler_ptr,
+ v1_match->first.version));
}
void FidoCableDiscovery::ConductEncryptionHandshake(
@@ -581,8 +542,8 @@ void FidoCableDiscovery::ValidateAuthenticatorHandshakeMessage(
}
}
-base::Optional<FidoCableDiscovery::Result>
-FidoCableDiscovery::GetCableDiscoveryData(const BluetoothDevice* device) const {
+base::Optional<FidoCableDiscovery::V1DiscoveryDataAndEID>
+FidoCableDiscovery::GetCableDiscoveryData(const BluetoothDevice* device) {
base::Optional<CableEidArray> maybe_eid_from_service_data =
MaybeGetEidFromServiceData(device);
std::vector<CableEidArray> uuids = GetUUIDs(device);
@@ -607,13 +568,12 @@ FidoCableDiscovery::GetCableDiscoveryData(const BluetoothDevice* device) const {
FIDO_LOG(DEBUG) << "New caBLE device " << address << ":";
}
- base::Optional<FidoCableDiscovery::Result> result;
+ base::Optional<FidoCableDiscovery::V1DiscoveryDataAndEID> result;
if (maybe_eid_from_service_data.has_value()) {
result =
GetCableDiscoveryDataFromAuthenticatorEid(*maybe_eid_from_service_data);
FIDO_LOG(DEBUG) << " Service data: "
<< ResultDebugString(*maybe_eid_from_service_data, result);
-
} else {
FIDO_LOG(DEBUG) << " Service data: <none>";
}
@@ -621,10 +581,14 @@ FidoCableDiscovery::GetCableDiscoveryData(const BluetoothDevice* device) const {
if (!uuids.empty()) {
FIDO_LOG(DEBUG) << " UUIDs:";
for (const auto& uuid : uuids) {
- auto discovery_data = GetCableDiscoveryDataFromAuthenticatorEid(uuid);
- FIDO_LOG(DEBUG) << " " << ResultDebugString(uuid, discovery_data);
- if (!result && discovery_data) {
- result = discovery_data;
+ auto eid_result = GetCableDiscoveryDataFromAuthenticatorEid(uuid);
+ FIDO_LOG(DEBUG) << " " << ResultDebugString(uuid, eid_result);
+ if (!result && eid_result) {
+ result = std::move(eid_result);
+ }
+
+ if (ble_observer_) {
+ ble_observer_->OnBLEAdvertSeen(device->GetAddress(), uuid);
}
}
}
@@ -678,51 +642,13 @@ std::vector<CableEidArray> FidoCableDiscovery::GetUUIDs(
return ret;
}
-base::Optional<FidoCableDiscovery::Result>
+base::Optional<FidoCableDiscovery::V1DiscoveryDataAndEID>
FidoCableDiscovery::GetCableDiscoveryDataFromAuthenticatorEid(
- CableEidArray authenticator_eid) const {
+ CableEidArray authenticator_eid) {
for (const auto& candidate : discovery_data_) {
if (candidate.version == CableDiscoveryData::Version::V1 &&
candidate.MatchV1(authenticator_eid)) {
- return Result(candidate, authenticator_eid, base::nullopt, base::nullopt);
- }
- }
-
- if (qr_generator_key_) {
- // Attempt to match |authenticator_eid| as the result of scanning a QR code.
- const int64_t current_tick = CableDiscoveryData::CurrentTimeTick();
- // kNumPreviousTicks is the number of previous ticks that will be accepted
- // as valid. Ticks are currently 256ms so the value of sixteen translates to
- // about four seconds.
- constexpr int kNumPreviousTicks = 16;
-
- for (int i = 0; i < kNumPreviousTicks; i++) {
- auto qr_secret = CableDiscoveryData::DeriveQRSecret(*qr_generator_key_,
- current_tick - i);
- auto identity_key_seed = CableDiscoveryData::DeriveIdentityKeySeed(
- *qr_generator_key_, current_tick - i);
- CableDiscoveryData candidate(qr_secret, identity_key_seed);
- CableEidArray decrypted;
- if (candidate.MatchV2(authenticator_eid, &decrypted)) {
- return Result(candidate, authenticator_eid, decrypted, i);
- }
- }
-
- if (base::Contains(noted_obsolete_eids_, authenticator_eid)) {
- std::array<uint8_t, kCableIdentityKeySeedSize> dummy_seed;
- for (int i = kNumPreviousTicks; i < 2 * kNumPreviousTicks; i++) {
- auto qr_secret = CableDiscoveryData::DeriveQRSecret(*qr_generator_key_,
- current_tick - i);
- CableDiscoveryData candidate(qr_secret, dummy_seed);
- CableEidArray decrypted;
- if (candidate.MatchV2(authenticator_eid, &decrypted)) {
- noted_obsolete_eids_.insert(authenticator_eid);
- FIDO_LOG(DEBUG)
- << "(EID " << base::HexEncode(authenticator_eid) << " is " << i
- << " ticks old and would be valid but for the cutoff)";
- break;
- }
- }
+ return V1DiscoveryDataAndEID(candidate, authenticator_eid);
}
}
@@ -748,7 +674,7 @@ void FidoCableDiscovery::StartInternal() {
// static
std::string FidoCableDiscovery::ResultDebugString(
const CableEidArray& eid,
- const base::Optional<FidoCableDiscovery::Result>& result) {
+ const base::Optional<FidoCableDiscovery::V1DiscoveryDataAndEID>& result) {
static const uint8_t kAppleContinuity[16] = {
0xd0, 0x61, 0x1e, 0x78, 0xbb, 0xb4, 0x45, 0x91,
0xa5, 0xf8, 0x48, 0x79, 0x10, 0xae, 0x43, 0x66,
@@ -789,22 +715,8 @@ std::string FidoCableDiscovery::ResultDebugString(
return ret;
}
- switch (result->discovery_data.version) {
- case CableDiscoveryData::Version::V1:
- ret += " (version one match";
- break;
- case CableDiscoveryData::Version::V2:
- ret += " (version two match";
- break;
- case CableDiscoveryData::Version::INVALID:
- NOTREACHED();
- }
-
- if (!result->ticks_back) {
- ret += " against pairing data)";
- } else {
- ret += " from QR, " + base::NumberToString(*result->ticks_back) +
- " tick(s) ago)";
+ if (result) {
+ ret += " (version one match)";
}
return ret;
diff --git a/chromium/device/fido/cable/fido_cable_discovery.h b/chromium/device/fido/cable/fido_cable_discovery.h
index 7104c5756e7..c7fbc54c8df 100644
--- a/chromium/device/fido/cable/fido_cable_discovery.h
+++ b/chromium/device/fido/cable/fido_cable_discovery.h
@@ -21,14 +21,9 @@
#include "device/bluetooth/bluetooth_adapter.h"
#include "device/fido/cable/cable_discovery_data.h"
#include "device/fido/cable/fido_cable_device.h"
+#include "device/fido/cable/v2_constants.h"
#include "device/fido/fido_device_discovery.h"
-namespace network {
-namespace mojom {
-class NetworkContext;
-}
-} // namespace network
-
namespace device {
class BluetoothDevice;
@@ -40,13 +35,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDiscovery
public BluetoothAdapter::Observer,
public FidoCableDevice::Observer {
public:
- FidoCableDiscovery(
- std::vector<CableDiscoveryData> discovery_data,
- base::Optional<QRGeneratorKey> qr_generator_key,
- base::Optional<
- base::RepeatingCallback<void(std::unique_ptr<CableDiscoveryData>)>>
- pairing_callback,
- network::mojom::NetworkContext* network_context);
+ FidoCableDiscovery(std::vector<CableDiscoveryData> discovery_data,
+ FidoDeviceDiscovery::BLEObserver* ble_observer);
~FidoCableDiscovery() override;
// FidoDeviceDiscovery:
@@ -66,26 +56,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDiscovery
private:
enum class CableV1DiscoveryEvent : int;
- // Result represents a successful match of a received EID against a specific
- // |FidoDiscoveryData|.
- struct Result {
- Result();
- Result(const CableDiscoveryData& in_discovery_data,
- const CableEidArray& in_eid,
- base::Optional<CableEidArray> decrypted_eid,
- base::Optional<int> ticks_back);
- Result(const Result&);
- ~Result();
-
- CableDiscoveryData discovery_data;
- CableEidArray eid;
-
- base::Optional<CableEidArray> decrypted_eid;
- // ticks_back is either |base::nullopt|, if the Result is from established
- // discovery pairings, or else contains the number of QR ticks back in time
- // against which the match was found.
- base::Optional<int> ticks_back;
- };
+ // V1DiscoveryDataAndEID represents a match against caBLEv1 pairing data. It
+ // contains the CableDiscoveryData that matched and the BLE EID that triggered
+ // the match.
+ using V1DiscoveryDataAndEID = std::pair<CableDiscoveryData, CableEidArray>;
// ObservedDeviceData contains potential EIDs observed from a BLE device. This
// information is kept in order to de-duplicate device-log entries and make
@@ -103,8 +77,9 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDiscovery
// ResultDebugString returns a string containing a hex dump of |eid| and a
// description of |result|, if present.
- static std::string ResultDebugString(const CableEidArray& eid,
- const base::Optional<Result>& result);
+ static std::string ResultDebugString(
+ const CableEidArray& eid,
+ const base::Optional<V1DiscoveryDataAndEID>& result);
static base::Optional<CableEidArray> MaybeGetEidFromServiceData(
const BluetoothDevice* device);
static std::vector<CableEidArray> GetUUIDs(const BluetoothDevice* device);
@@ -138,10 +113,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDiscovery
FidoCableHandshakeHandler* handshake_handler,
base::Optional<std::vector<uint8_t>> handshake_response);
- base::Optional<Result> GetCableDiscoveryData(
- const BluetoothDevice* device) const;
- base::Optional<Result> GetCableDiscoveryDataFromAuthenticatorEid(
- CableEidArray authenticator_eid) const;
+ base::Optional<V1DiscoveryDataAndEID> GetCableDiscoveryData(
+ const BluetoothDevice* device);
+ base::Optional<V1DiscoveryDataAndEID>
+ GetCableDiscoveryDataFromAuthenticatorEid(CableEidArray authenticator_eid);
void RecordCableV1DiscoveryEventOnce(CableV1DiscoveryEvent event);
// FidoDeviceDiscovery:
@@ -165,18 +140,19 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDiscovery
std::unique_ptr<BluetoothDiscoverySession> discovery_session_;
std::vector<CableDiscoveryData> discovery_data_;
+ FidoDeviceDiscovery::BLEObserver* const ble_observer_;
// 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
- // not completely effective.
+ // 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 not completely effective.
std::set<std::string> active_devices_;
- base::Optional<QRGeneratorKey> qr_generator_key_;
+ base::Optional<std::array<uint8_t, cablev2::kQRKeySize>> qr_generator_key_;
// Note that on Windows, |advertisements_| is the only reference holder of
// BluetoothAdvertisement.
@@ -186,20 +162,12 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoCableDiscovery
std::vector<std::pair<std::unique_ptr<FidoCableDevice>,
std::unique_ptr<FidoCableHandshakeHandler>>>
active_handshakes_;
- base::Optional<
- base::RepeatingCallback<void(std::unique_ptr<CableDiscoveryData>)>>
- pairing_callback_;
- network::mojom::NetworkContext* const network_context_;
// observed_devices_ caches the information from observed caBLE devices so
// that the device-log isn't spammed.
- mutable base::flat_map<std::string, std::unique_ptr<ObservedDeviceData>>
+ 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_;
-
bool has_v1_discovery_data_ = false;
base::flat_set<CableV1DiscoveryEvent> recorded_events_;
diff --git a/chromium/device/fido/cable/fido_cable_discovery_unittest.cc b/chromium/device/fido/cable/fido_cable_discovery_unittest.cc
index 9ac27ec3d42..d45b9d83f6e 100644
--- a/chromium/device/fido/cable/fido_cable_discovery_unittest.cc
+++ b/chromium/device/fido/cable/fido_cable_discovery_unittest.cc
@@ -326,9 +326,7 @@ class FakeFidoCableDiscovery : public FidoCableDiscovery {
explicit FakeFidoCableDiscovery(
std::vector<CableDiscoveryData> discovery_data)
: FidoCableDiscovery(std::move(discovery_data),
- BogusQRGeneratorKey(),
- /*pairing_callback=*/base::nullopt,
- /*network_context=*/nullptr) {}
+ /*ble_observer=*/nullptr) {}
~FakeFidoCableDiscovery() override = default;
private:
@@ -342,12 +340,6 @@ class FakeFidoCableDiscovery : public FidoCableDiscovery {
return std::make_unique<FakeHandshakeHandler>(
device, nonce, discovery_data.v1->session_pre_key);
}
-
- static std::array<uint8_t, 32> BogusQRGeneratorKey() {
- std::array<uint8_t, 32> ret;
- memset(ret.data(), 0, ret.size());
- return ret;
- }
};
} // namespace
diff --git a/chromium/device/fido/cable/fido_tunnel_device.cc b/chromium/device/fido/cable/fido_tunnel_device.cc
index 457971a0ba5..f557e4590d9 100644
--- a/chromium/device/fido/cable/fido_tunnel_device.cc
+++ b/chromium/device/fido/cable/fido_tunnel_device.cc
@@ -5,16 +5,43 @@
#include "device/fido/cable/fido_tunnel_device.h"
#include "base/strings/string_number_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/cbor/values.h"
+#include "components/cbor/writer.h"
#include "components/device_event_log/device_event_log.h"
#include "crypto/random.h"
+#include "device/fido/cable/cable_discovery_data.h"
+#include "device/fido/cbor_extract.h"
#include "device/fido/fido_constants.h"
+#include "device/fido/fido_parsing_utils.h"
#include "net/traffic_annotation/network_traffic_annotation.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/hkdf.h"
+using device::cbor_extract::IntKey;
+using device::cbor_extract::Is;
+using device::cbor_extract::StepOrByte;
+using device::cbor_extract::Stop;
+
namespace device {
namespace cablev2 {
+namespace {
+
+std::array<uint8_t, 8> RandomId() {
+ std::array<uint8_t, 8> ret;
+ crypto::RandBytes(ret);
+ return ret;
+}
+
+} // namespace
+
+FidoTunnelDevice::QRInfo::QRInfo() = default;
+FidoTunnelDevice::QRInfo::~QRInfo() = default;
+FidoTunnelDevice::PairedInfo::PairedInfo() = default;
+FidoTunnelDevice::PairedInfo::~PairedInfo() = default;
+
constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("cablev2_websocket_from_client", R"(
semantics {
@@ -40,34 +67,37 @@ constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
"triggered by significant user action."
policy_exception_justification:
"No policy provided because the operation is triggered by "
- " significant user action."
+ " significant user action. No background activity occurs."
})");
FidoTunnelDevice::FidoTunnelDevice(
network::mojom::NetworkContext* network_context,
- const CableDiscoveryData::V2Data& v2data,
+ base::OnceCallback<void(std::unique_ptr<Pairing>)> pairing_callback,
+ base::span<const uint8_t> secret,
+ base::span<const uint8_t, kQRSeedSize> local_identity_seed,
const CableEidArray& eid,
const CableEidArray& decrypted_eid)
- : v2data_(v2data) {
+ : info_(absl::in_place_type<QRInfo>), id_(RandomId()) {
DCHECK(eid::IsValid(decrypted_eid));
- crypto::RandBytes(id_);
-
const eid::Components components = eid::ToComponents(decrypted_eid);
- nonce_and_eid_.first = components.nonce;
- nonce_and_eid_.second = eid;
+
+ QRInfo& info = absl::get<QRInfo>(info_);
+ info.pairing_callback = std::move(pairing_callback);
+ info.eid = eid;
+ info.local_identity_seed =
+ fido_parsing_utils::Materialize(local_identity_seed);
+ info.tunnel_server_domain = components.tunnel_server_domain;
+
+ info.psk = Derive<EXTENT(info.psk)>(secret, components.nonce,
+ DerivedValueType::kPSK);
std::array<uint8_t, 16> tunnel_id;
- bool ok = HKDF(tunnel_id.data(), tunnel_id.size(), EVP_sha256(),
- v2data_.tunnel_id_gen_key.data(),
- v2data_.tunnel_id_gen_key.size(), components.nonce.data(),
- components.nonce.size(), /*info=*/nullptr, 0);
- DCHECK(ok);
-
- const GURL url(cablev2::tunnelserver::GetURL(
- components.tunnel_server_domain, cablev2::tunnelserver::Action::kConnect,
- tunnel_id));
- FIDO_LOG(DEBUG) << "Connecting caBLEv2 tunnel: " << url
- << " shard: " << static_cast<int>(components.shard_id);
+ tunnel_id = Derive<EXTENT(tunnel_id)>(secret, components.nonce,
+ DerivedValueType::kTunnelID);
+
+ const GURL url(tunnelserver::GetConnectURL(components.tunnel_server_domain,
+ components.routing_id, tunnel_id));
+ FIDO_LOG(DEBUG) << GetId() << ": connecting caBLEv2 tunnel: " << url;
websocket_client_ = std::make_unique<device::cablev2::WebSocketAdapter>(
base::BindOnce(&FidoTunnelDevice::OnTunnelReady, base::Unretained(this)),
@@ -84,20 +114,102 @@ FidoTunnelDevice::FidoTunnelDevice(
mojo::NullRemote());
}
+FidoTunnelDevice::FidoTunnelDevice(
+ network::mojom::NetworkContext* network_context,
+ std::unique_ptr<Pairing> pairing)
+ : info_(absl::in_place_type<PairedInfo>), id_(RandomId()) {
+ uint8_t client_nonce[kClientNonceSize];
+ crypto::RandBytes(client_nonce);
+
+ cbor::Value::MapValue client_payload;
+ client_payload.emplace(1, pairing->id);
+ client_payload.emplace(2, base::span<const uint8_t>(client_nonce));
+ const base::Optional<std::vector<uint8_t>> client_payload_bytes =
+ cbor::Writer::Write(cbor::Value(std::move(client_payload)));
+ CHECK(client_payload_bytes.has_value());
+ const std::string client_payload_hex = base::HexEncode(*client_payload_bytes);
+
+ PairedInfo& info = absl::get<PairedInfo>(info_);
+ info.eid_encryption_key = Derive<EXTENT(info.eid_encryption_key)>(
+ pairing->secret, client_nonce, DerivedValueType::kEIDKey);
+ info.peer_identity = pairing->peer_public_key_x962;
+ info.secret = pairing->secret;
+
+ const GURL url = tunnelserver::GetContactURL(pairing->tunnel_server_domain,
+ pairing->contact_id);
+ FIDO_LOG(DEBUG) << GetId() << ": connecting caBLEv2 tunnel: " << url;
+
+ websocket_client_ = std::make_unique<device::cablev2::WebSocketAdapter>(
+ base::BindOnce(&FidoTunnelDevice::OnTunnelReady, base::Unretained(this)),
+ base::BindRepeating(&FidoTunnelDevice::OnTunnelData,
+ base::Unretained(this)));
+ std::vector<network::mojom::HttpHeaderPtr> headers;
+ headers.emplace_back(network::mojom::HttpHeader::New(
+ kCableClientPayloadHeader, client_payload_hex));
+ network_context->CreateWebSocket(
+ url, {kCableWebSocketProtocol}, net::SiteForCookies(),
+ net::IsolationInfo(), std::move(headers),
+ network::mojom::kBrowserProcessId,
+ /*render_frame_id=*/0, url::Origin::Create(url),
+ network::mojom::kWebSocketOptionBlockAllCookies,
+ net::MutableNetworkTrafficAnnotationTag(kTrafficAnnotation),
+ websocket_client_->BindNewHandshakeClientPipe(), mojo::NullRemote(),
+ mojo::NullRemote());
+}
+
FidoTunnelDevice::~FidoTunnelDevice() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
+bool FidoTunnelDevice::MatchEID(const CableEidArray& eid) {
+ PairedInfo& info = absl::get<PairedInfo>(info_);
+
+ AES_KEY key;
+ CHECK(AES_set_decrypt_key(info.eid_encryption_key.data(),
+ /*bits=*/8 * info.eid_encryption_key.size(),
+ &key) == 0);
+ CableEidArray plaintext;
+ static_assert(EXTENT(plaintext) == AES_BLOCK_SIZE, "EIDs are not AES blocks");
+ AES_decrypt(/*in=*/eid.data(), /*out=*/plaintext.data(), &key);
+
+ if (!eid::IsValid(plaintext)) {
+ return false;
+ }
+
+ const eid::Components components = eid::ToComponents(plaintext);
+ static_assert(EXTENT(components.routing_id) == 3, "");
+ if (components.routing_id[0] || components.routing_id[1] ||
+ components.routing_id[2]) {
+ return false;
+ }
+
+ info.eid = eid;
+ info.psk = Derive<EXTENT(*info.psk)>(info.secret, components.nonce,
+ DerivedValueType::kPSK);
+
+ if (state_ == State::kWaitingForEID) {
+ // The handshake message has already been received. It can now be answered.
+ ProcessHandshake(*info.handshake_message);
+ }
+
+ return true;
+}
+
FidoDevice::CancelToken FidoTunnelDevice::DeviceTransact(
std::vector<uint8_t> command,
DeviceCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!callback_);
- pending_message_ = std::move(command);
- callback_ = std::move(callback);
- if (state_ == State::kHandshakeProcessed || state_ == State::kReady) {
- MaybeFlushPendingMessage();
+ if (state_ == State::kError) {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), base::nullopt));
+ } else {
+ pending_message_ = std::move(command);
+ callback_ = std::move(callback);
+ if (state_ == State::kHandshakeProcessed || state_ == State::kReady) {
+ MaybeFlushPendingMessage();
+ }
}
// TODO: cancelation would be useful, but it depends on the GMSCore action
@@ -124,12 +236,14 @@ base::WeakPtr<FidoDevice> FidoTunnelDevice::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
-void FidoTunnelDevice::OnTunnelReady(bool ok,
- base::Optional<uint8_t> shard_id) {
+void FidoTunnelDevice::OnTunnelReady(
+ bool ok,
+ base::Optional<std::array<uint8_t, kRoutingIdSize>> routing_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(State::kConnecting, state_);
if (!ok) {
+ FIDO_LOG(DEBUG) << GetId() << ": tunnel failed to connect";
OnError();
return;
}
@@ -148,29 +262,15 @@ void FidoTunnelDevice::OnTunnelData(
switch (state_) {
case State::kError:
+ break;
+
case State::kConnecting:
- NOTREACHED();
+ case State::kWaitingForEID:
+ OnError();
break;
case State::kConnected: {
- std::vector<uint8_t> response;
- base::Optional<std::pair<std::unique_ptr<Crypter>, std::vector<uint8_t>>>
- result(cablev2::RespondToHandshake(
- v2data_.psk_gen_key, nonce_and_eid_, v2data_.local_identity_seed,
- base::nullopt, *data, &response));
- if (!result || result->second.empty()) {
- FIDO_LOG(ERROR) << "caBLEv2 handshake failed";
- OnError();
- return;
- }
-
- FIDO_LOG(DEBUG) << "caBLEv2 handshake successful";
- websocket_client_->Write(response);
- crypter_ = std::move(result->first);
- getinfo_response_bytes_ = std::move(result->second);
- state_ = State::kHandshakeProcessed;
-
- MaybeFlushPendingMessage();
+ ProcessHandshake(*data);
break;
}
@@ -179,18 +279,34 @@ void FidoTunnelDevice::OnTunnelData(
// information.
std::vector<uint8_t> decrypted;
if (!crypter_->Decrypt(*data, &decrypted)) {
- FIDO_LOG(ERROR) << "decryption failed for caBLE pairing message";
+ FIDO_LOG(ERROR) << GetId()
+ << ": decryption failed for caBLE pairing message";
OnError();
return;
}
base::Optional<cbor::Value> payload = DecodePaddedCBORMap(decrypted);
- if (!payload) {
- FIDO_LOG(ERROR) << "decode failed for caBLE pairing message";
+ if (!payload || !payload->is_map()) {
+ FIDO_LOG(ERROR) << GetId()
+ << ": decode failed for caBLE pairing message";
OnError();
return;
}
- // TODO: pairing not yet handled.
+ // The map may be empty if the peer doesn't wish to send pairing
+ // information.
+ if (!payload->GetMap().empty()) {
+ QRInfo& info = absl::get<QRInfo>(info_);
+ base::Optional<std::unique_ptr<Pairing>> maybe_pairing =
+ Pairing::Parse(*payload, info.tunnel_server_domain,
+ info.local_identity_seed, *info.handshake_hash);
+ if (!maybe_pairing) {
+ FIDO_LOG(ERROR) << GetId() << ": invalid caBLE pairing message";
+ OnError();
+ return;
+ }
+
+ std::move(info.pairing_callback).Run(std::move(*maybe_pairing));
+ }
state_ = State::kReady;
break;
@@ -204,7 +320,7 @@ void FidoTunnelDevice::OnTunnelData(
std::vector<uint8_t> plaintext;
if (!crypter_->Decrypt(*data, &plaintext)) {
- FIDO_LOG(ERROR) << "decryption failed for caBLE message";
+ FIDO_LOG(ERROR) << GetId() << ": decryption failed for caBLE message";
OnError();
return;
}
@@ -215,6 +331,57 @@ void FidoTunnelDevice::OnTunnelData(
}
}
+void FidoTunnelDevice::ProcessHandshake(base::span<const uint8_t> data) {
+ DCHECK(state_ == State::kWaitingForEID || state_ == State::kConnected);
+
+ std::vector<uint8_t> response;
+ base::Optional<ResponderResult> result;
+
+ if (auto* info = absl::get_if<QRInfo>(&info_)) {
+ base::Optional<ResponderResult> inner_result(cablev2::RespondToHandshake(
+ info->psk, info->eid, info->local_identity_seed, base::nullopt, data,
+ &response));
+ if (inner_result) {
+ result.emplace(std::move(*inner_result));
+ }
+ state_ = State::kHandshakeProcessed;
+ } else if (auto* info = absl::get_if<PairedInfo>(&info_)) {
+ if (!info->eid) {
+ DCHECK_EQ(state_, State::kConnected);
+ state_ = State::kWaitingForEID;
+ info->handshake_message = fido_parsing_utils::Materialize(data);
+ return;
+ }
+
+ base::Optional<ResponderResult> inner_result(
+ cablev2::RespondToHandshake(*info->psk, *info->eid,
+ /*local_identity=*/base::nullopt,
+ info->peer_identity, data, &response));
+ if (inner_result) {
+ result.emplace(std::move(*inner_result));
+ }
+ state_ = State::kReady;
+ } else {
+ CHECK(false);
+ }
+
+ if (!result || result->getinfo_bytes.empty()) {
+ FIDO_LOG(ERROR) << GetId() << ": caBLEv2 handshake failed";
+ OnError();
+ return;
+ }
+
+ FIDO_LOG(DEBUG) << GetId() << ": caBLEv2 handshake successful";
+ websocket_client_->Write(response);
+ if (auto* info = absl::get_if<QRInfo>(&info_)) {
+ info->handshake_hash = result->handshake_hash;
+ }
+ crypter_ = std::move(result->crypter);
+ getinfo_response_bytes_ = std::move(result->getinfo_bytes);
+
+ MaybeFlushPendingMessage();
+}
+
void FidoTunnelDevice::OnError() {
state_ = State::kError;
websocket_client_.reset();
@@ -238,7 +405,8 @@ void FidoTunnelDevice::MaybeFlushPendingMessage() {
reply.push_back(static_cast<uint8_t>(CtapDeviceResponseCode::kSuccess));
reply.insert(reply.end(), getinfo_response_bytes_.begin(),
getinfo_response_bytes_.end());
- std::move(callback_).Run(std::move(reply));
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback_), std::move(reply)));
} else if (crypter_->Encrypt(&pending)) {
websocket_client_->Write(pending);
}
diff --git a/chromium/device/fido/cable/fido_tunnel_device.h b/chromium/device/fido/cable/fido_tunnel_device.h
index 6e9e8be8d14..df878df6c02 100644
--- a/chromium/device/fido/cable/fido_tunnel_device.h
+++ b/chromium/device/fido/cable/fido_tunnel_device.h
@@ -7,11 +7,12 @@
#include <vector>
+#include "base/callback_forward.h"
#include "base/sequence_checker.h"
-#include "device/fido/cable/cable_discovery_data.h"
-#include "device/fido/cable/v2_handshake.h"
+#include "device/fido/cable/v2_constants.h"
#include "device/fido/cable/websocket_adapter.h"
#include "device/fido/fido_device.h"
+#include "third_party/abseil-cpp/absl/types/variant.h"
namespace network {
namespace mojom {
@@ -24,15 +25,30 @@ namespace cablev2 {
class Crypter;
class WebSocketAdapter;
+struct Pairing;
class COMPONENT_EXPORT(DEVICE_FIDO) FidoTunnelDevice : public FidoDevice {
public:
+ // This constructor is used for QR-initiated connections.
+ FidoTunnelDevice(
+ network::mojom::NetworkContext* network_context,
+ base::OnceCallback<void(std::unique_ptr<Pairing>)> pairing_callback,
+ base::span<const uint8_t> secret,
+ base::span<const uint8_t, kQRSeedSize> local_identity_seed,
+ const CableEidArray& eid,
+ const CableEidArray& decrypted_eid);
+
+ // This constructor is used for pairing-initiated connections.
FidoTunnelDevice(network::mojom::NetworkContext* network_context,
- const CableDiscoveryData::V2Data& v2data,
- const CableEidArray& eid,
- const CableEidArray& decrypted_eid);
+ std::unique_ptr<Pairing> pairing);
+
~FidoTunnelDevice() override;
+ // MatchEID is only valid for a pairing-initiated connection. It returns true
+ // if the given |eid| matched this pending tunnel and thus this device is now
+ // ready.
+ bool MatchEID(const CableEidArray& eid);
+
// FidoDevice:
CancelToken DeviceTransact(std::vector<uint8_t> command,
DeviceCallback callback) override;
@@ -45,20 +61,51 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoTunnelDevice : public FidoDevice {
enum class State {
kConnecting,
kConnected,
+ kWaitingForEID,
kHandshakeProcessed,
kReady,
kError,
};
- void OnTunnelReady(bool ok, base::Optional<uint8_t> shard_id);
+ struct QRInfo {
+ QRInfo();
+ ~QRInfo();
+ QRInfo(const QRInfo&) = delete;
+ QRInfo& operator=(const QRInfo&) = delete;
+
+ CableEidArray eid;
+ std::array<uint8_t, 32> psk;
+ base::OnceCallback<void(std::unique_ptr<Pairing>)> pairing_callback;
+ std::array<uint8_t, kQRSeedSize> local_identity_seed;
+ uint32_t tunnel_server_domain;
+ base::Optional<HandshakeHash> handshake_hash;
+ };
+
+ struct PairedInfo {
+ PairedInfo();
+ ~PairedInfo();
+ PairedInfo(const PairedInfo&) = delete;
+ PairedInfo& operator=(const PairedInfo&) = delete;
+
+ std::array<uint8_t, 32> eid_encryption_key;
+ std::array<uint8_t, kP256X962Length> peer_identity;
+ std::vector<uint8_t> secret;
+ base::Optional<CableEidArray> eid;
+ base::Optional<std::array<uint8_t, 32>> psk;
+ base::Optional<std::vector<uint8_t>> handshake_message;
+ };
+
+ void OnTunnelReady(
+ bool ok,
+ base::Optional<std::array<uint8_t, kRoutingIdSize>> routing_id);
void OnTunnelData(base::Optional<base::span<const uint8_t>> data);
+ void ProcessHandshake(base::span<const uint8_t> data);
void OnError();
void MaybeFlushPendingMessage();
State state_ = State::kConnecting;
- std::array<uint8_t, 8> id_;
- const CableDiscoveryData::V2Data v2data_;
- cablev2::NonceAndEID nonce_and_eid_;
+ absl::variant<QRInfo, PairedInfo> info_;
+ const std::array<uint8_t, 8> id_;
std::unique_ptr<WebSocketAdapter> websocket_client_;
std::unique_ptr<Crypter> crypter_;
std::vector<uint8_t> getinfo_response_bytes_;
diff --git a/chromium/device/fido/cable/noise.cc b/chromium/device/fido/cable/noise.cc
index 7926500005f..a2bdf416e41 100644
--- a/chromium/device/fido/cable/noise.cc
+++ b/chromium/device/fido/cable/noise.cc
@@ -124,6 +124,10 @@ base::Optional<std::vector<uint8_t>> Noise::DecryptAndHash(
return plaintext;
}
+std::array<uint8_t, 32> Noise::handshake_hash() const {
+ return h_;
+}
+
void Noise::MixHashPoint(const EC_POINT* point) {
uint8_t x962[kP256X962Length];
bssl::UniquePtr<EC_GROUP> p256(
diff --git a/chromium/device/fido/cable/noise.h b/chromium/device/fido/cable/noise.h
index 0372f452b82..2cd584074cf 100644
--- a/chromium/device/fido/cable/noise.h
+++ b/chromium/device/fido/cable/noise.h
@@ -41,6 +41,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) Noise {
std::vector<uint8_t> EncryptAndHash(base::span<const uint8_t> plaintext);
base::Optional<std::vector<uint8_t>> DecryptAndHash(
base::span<const uint8_t> ciphertext);
+ std::array<uint8_t, 32> handshake_hash() const;
// MaxHashPoint calls |MixHash| with the uncompressed, X9.62 serialization of
// |point|.
diff --git a/chromium/device/fido/cable/v2_authenticator.cc b/chromium/device/fido/cable/v2_authenticator.cc
new file mode 100644
index 00000000000..8e4225fe74c
--- /dev/null
+++ b/chromium/device/fido/cable/v2_authenticator.cc
@@ -0,0 +1,887 @@
+// 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/v2_authenticator.h"
+
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/strings/string_number_conversions.h"
+#include "components/cbor/diagnostic_writer.h"
+#include "components/cbor/reader.h"
+#include "components/cbor/values.h"
+#include "components/cbor/writer.h"
+#include "components/device_event_log/device_event_log.h"
+#include "crypto/random.h"
+#include "device/fido/cable/v2_handshake.h"
+#include "device/fido/cable/websocket_adapter.h"
+#include "device/fido/cbor_extract.h"
+#include "device/fido/fido_constants.h"
+#include "device/fido/fido_parsing_utils.h"
+#include "net/base/isolation_info.h"
+#include "net/cookies/site_for_cookies.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/public/mojom/network_context.mojom.h"
+#include "third_party/boringssl/src/include/openssl/aes.h"
+#include "third_party/boringssl/src/include/openssl/ec_key.h"
+#include "third_party/boringssl/src/include/openssl/obj.h"
+
+namespace device {
+namespace cablev2 {
+namespace authenticator {
+
+using device::CtapDeviceResponseCode;
+using device::CtapRequestCommand;
+using device::cbor_extract::IntKey;
+using device::cbor_extract::Is;
+using device::cbor_extract::Map;
+using device::cbor_extract::StepOrByte;
+using device::cbor_extract::Stop;
+using device::cbor_extract::StringKey;
+
+namespace {
+
+constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
+ net::DefineNetworkTrafficAnnotation("cablev2_websocket_from_authenticator",
+ R"(semantics {
+ sender: "Phone as a Security Key"
+ description:
+ "Chrome on a phone can communicate with other devices for the "
+ "purpose of using the phone as a security key. This WebSocket "
+ "connection is made to a Google service that aids in the exchange "
+ "of data with the other device. The service carries only "
+ "end-to-end encrypted data where the keys are shared directly "
+ "between the two devices via QR code and Bluetooth broadcast."
+ trigger:
+ "The user scans a QR code, displayed on the other device, and "
+ "confirms their desire to communicate with it."
+ data: "Only encrypted data that the service does not have the keys "
+ "for."
+ destination: GOOGLE_OWNED_SERVICE
+ }
+ policy {
+ cookies_allowed: NO
+ setting: "Not controlled by a setting because the operation is "
+ "triggered by significant user action."
+ policy_exception_justification:
+ "No policy provided because the operation is triggered by "
+ " significant user action. No background activity occurs."
+ })");
+
+// kTunnelServer is the hardcoded tunnel server that phones will use for network
+// communication. This specifies a Google service and the short domain need is
+// necessary to fit within a BLE advert.
+constexpr uint32_t kTunnelServer = device::cablev2::tunnelserver::EncodeDomain(
+ "xyi3",
+ device::cablev2::tunnelserver::TLD::COM);
+
+struct MakeCredRequest {
+ const std::vector<uint8_t>* client_data_hash;
+ const std::string* rp_id;
+ const std::vector<uint8_t>* user_id;
+ const cbor::Value::ArrayValue* cred_params;
+ const cbor::Value::ArrayValue* excluded_credentials;
+ const std::string* origin;
+ const std::vector<uint8_t>* challenge;
+};
+
+static constexpr StepOrByte<MakeCredRequest> kMakeCredParseSteps[] = {
+ // clang-format off
+ ELEMENT(Is::kRequired, MakeCredRequest, client_data_hash),
+ IntKey<MakeCredRequest>(1),
+
+ Map<MakeCredRequest>(),
+ IntKey<MakeCredRequest>(2),
+ ELEMENT(Is::kRequired, MakeCredRequest, rp_id),
+ StringKey<MakeCredRequest>(), 'i', 'd', '\0',
+ Stop<MakeCredRequest>(),
+
+ Map<MakeCredRequest>(),
+ IntKey<MakeCredRequest>(3),
+ ELEMENT(Is::kRequired, MakeCredRequest, user_id),
+ StringKey<MakeCredRequest>(), 'i', 'd', '\0',
+ Stop<MakeCredRequest>(),
+
+ ELEMENT(Is::kRequired, MakeCredRequest, cred_params),
+ IntKey<MakeCredRequest>(4),
+ ELEMENT(Is::kOptional, MakeCredRequest, excluded_credentials),
+ IntKey<MakeCredRequest>(5),
+
+ // TODO: remove once the FIDO API can handle clientDataJSON
+ Map<MakeCredRequest>(),
+ IntKey<MakeCredRequest>(6),
+ Map<MakeCredRequest>(),
+ StringKey<MakeCredRequest>(),
+ 'g', 'o', 'o', 'g', 'l', 'e', 'A', 'n', 'd', 'r', 'o', 'i', 'd',
+ 'C', 'l', 'i', 'e', 'n', 't', 'D', 'a', 't', 'a', '\0',
+ ELEMENT(Is::kRequired, MakeCredRequest, origin),
+ IntKey<MakeCredRequest>(2),
+
+ ELEMENT(Is::kRequired, MakeCredRequest, challenge),
+ IntKey<MakeCredRequest>(3),
+ Stop<MakeCredRequest>(),
+ Stop<MakeCredRequest>(),
+
+ Stop<MakeCredRequest>(),
+ // clang-format on
+};
+
+struct AttestationObject {
+ const std::string* fmt;
+ const std::vector<uint8_t>* auth_data;
+ const cbor::Value* statement;
+};
+
+static constexpr StepOrByte<AttestationObject> kAttObjParseSteps[] = {
+ // clang-format off
+ ELEMENT(Is::kRequired, AttestationObject, fmt),
+ StringKey<AttestationObject>(), 'f', 'm', 't', '\0',
+
+ ELEMENT(Is::kRequired, AttestationObject, auth_data),
+ StringKey<AttestationObject>(), 'a', 'u', 't', 'h', 'D', 'a', 't', 'a',
+ '\0',
+
+ ELEMENT(Is::kRequired, AttestationObject, statement),
+ StringKey<AttestationObject>(), 'a', 't', 't', 'S', 't', 'm', 't', '\0',
+ Stop<AttestationObject>(),
+ // clang-format on
+};
+
+struct GetAssertionRequest {
+ const std::string* rp_id;
+ const std::vector<uint8_t>* client_data_hash;
+ const cbor::Value::ArrayValue* allowed_credentials;
+ const std::string* origin;
+ const std::vector<uint8_t>* challenge;
+};
+
+static constexpr StepOrByte<GetAssertionRequest> kGetAssertionParseSteps[] = {
+ // clang-format off
+ ELEMENT(Is::kRequired, GetAssertionRequest, rp_id),
+ IntKey<GetAssertionRequest>(1),
+
+ ELEMENT(Is::kRequired, GetAssertionRequest, client_data_hash),
+ IntKey<GetAssertionRequest>(2),
+
+ ELEMENT(Is::kOptional, GetAssertionRequest, allowed_credentials),
+ IntKey<GetAssertionRequest>(3),
+
+ // TODO: remove once the FIDO API can handle clientDataJSON
+ Map<GetAssertionRequest>(),
+ IntKey<GetAssertionRequest>(4),
+ Map<GetAssertionRequest>(),
+ StringKey<GetAssertionRequest>(),
+ 'g', 'o', 'o', 'g', 'l', 'e', 'A', 'n', 'd', 'r', 'o', 'i', 'd',
+ 'C', 'l', 'i', 'e', 'n', 't', 'D', 'a', 't', 'a', '\0',
+ ELEMENT(Is::kRequired, GetAssertionRequest, origin),
+ IntKey<GetAssertionRequest>(2),
+
+ ELEMENT(Is::kRequired, GetAssertionRequest, challenge),
+ IntKey<GetAssertionRequest>(3),
+ Stop<GetAssertionRequest>(),
+ Stop<GetAssertionRequest>(),
+
+ Stop<GetAssertionRequest>(),
+ // clang-format on
+};
+
+// BuildGetInfoResponse returns a CBOR-encoded getInfo response.
+std::vector<uint8_t> BuildGetInfoResponse() {
+ std::array<uint8_t, device::kAaguidLength> aaguid{};
+ std::vector<cbor::Value> versions;
+ versions.emplace_back("FIDO_2_0");
+ std::vector<cbor::Value> extensions;
+ extensions.emplace_back(device::kExtensionAndroidClientData);
+ // TODO: should be based on whether a screen-lock is enabled.
+ cbor::Value::MapValue options;
+ options.emplace("uv", true);
+
+ cbor::Value::MapValue response_map;
+ response_map.emplace(1, std::move(versions));
+ response_map.emplace(2, std::move(extensions));
+ response_map.emplace(3, aaguid);
+ response_map.emplace(4, std::move(options));
+
+ return cbor::Writer::Write(cbor::Value(std::move(response_map))).value();
+}
+
+std::array<uint8_t, device::cablev2::kNonceSize> RandomNonce() {
+ std::array<uint8_t, device::cablev2::kNonceSize> ret;
+ crypto::RandBytes(ret);
+ return ret;
+}
+
+using GeneratePairingDataCallback = base::OnceCallback<std::vector<uint8_t>(
+ base::span<const uint8_t, device::kP256X962Length> peer_public_key_x962,
+ device::cablev2::HandshakeHash)>;
+
+// TunnelTransport is a transport that uses WebSockets to talk to a cloud
+// service and uses BLE adverts to show proximity.
+class TunnelTransport : public Transport {
+ public:
+ TunnelTransport(
+ Platform* platform,
+ network::mojom::NetworkContext* network_context,
+ base::span<const uint8_t> secret,
+ base::span<const uint8_t, device::kP256X962Length> peer_identity,
+ GeneratePairingDataCallback generate_pairing_data)
+ : platform_(platform),
+ nonce_(RandomNonce()),
+ tunnel_id_(device::cablev2::Derive<EXTENT(tunnel_id_)>(
+ secret,
+ nonce_,
+ DerivedValueType::kTunnelID)),
+ eid_key_(device::cablev2::Derive<EXTENT(eid_key_)>(
+ secret,
+ base::span<const uint8_t>(),
+ device::cablev2::DerivedValueType::kEIDKey)),
+ network_context_(network_context),
+ peer_identity_(device::fido_parsing_utils::Materialize(peer_identity)),
+ generate_pairing_data_(std::move(generate_pairing_data)) {
+ DCHECK_EQ(state_, State::kNone);
+
+ state_ = State::kConnecting;
+
+ std::array<uint8_t, device::cablev2::kPSKSize> psk;
+ psk = device::cablev2::Derive<EXTENT(psk)>(
+ secret, nonce_, device::cablev2::DerivedValueType::kPSK);
+ handshaker_ = std::make_unique<device::cablev2::HandshakeInitiator>(
+ psk, peer_identity, /*local_identity=*/nullptr);
+
+ websocket_client_ = std::make_unique<device::cablev2::WebSocketAdapter>(
+ base::BindOnce(&TunnelTransport::OnTunnelReady, base::Unretained(this)),
+ base::BindRepeating(&TunnelTransport::OnTunnelData,
+ base::Unretained(this)));
+ target_ = device::cablev2::tunnelserver::GetNewTunnelURL(kTunnelServer,
+ tunnel_id_);
+ }
+
+ TunnelTransport(
+ Platform* platform,
+ network::mojom::NetworkContext* network_context,
+ base::span<const uint8_t> secret,
+ base::span<const uint8_t, device::cablev2::kClientNonceSize> client_nonce,
+ std::array<uint8_t, device::cablev2::kRoutingIdSize> routing_id,
+ base::span<const uint8_t, 16> tunnel_id,
+ bssl::UniquePtr<EC_KEY> local_identity)
+ : platform_(platform),
+ nonce_(RandomNonce()),
+ tunnel_id_(fido_parsing_utils::Materialize(tunnel_id)),
+ eid_key_(device::cablev2::Derive<EXTENT(eid_key_)>(
+ secret,
+ client_nonce,
+ device::cablev2::DerivedValueType::kEIDKey)),
+ network_context_(network_context) {
+ DCHECK_EQ(state_, State::kNone);
+
+ state_ = State::kConnectingPaired;
+
+ std::array<uint8_t, device::cablev2::kPSKSize> psk;
+ psk = device::cablev2::Derive<EXTENT(psk)>(
+ secret, nonce_, device::cablev2::DerivedValueType::kPSK);
+ handshaker_ = std::make_unique<device::cablev2::HandshakeInitiator>(
+ psk, /*peer_identity=*/base::nullopt, std::move(local_identity));
+
+ websocket_client_ = std::make_unique<device::cablev2::WebSocketAdapter>(
+ base::BindOnce(&TunnelTransport::OnTunnelReady, base::Unretained(this)),
+ base::BindRepeating(&TunnelTransport::OnTunnelData,
+ base::Unretained(this)));
+ target_ = device::cablev2::tunnelserver::GetConnectURL(
+ kTunnelServer, routing_id, tunnel_id);
+ }
+
+ // Transport:
+
+ void StartReading(
+ base::RepeatingCallback<void(base::Optional<std::vector<uint8_t>>)>
+ read_callback) override {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!read_callback_);
+
+ read_callback_ = std::move(read_callback);
+
+ network_context_->CreateWebSocket(
+ target_, {device::kCableWebSocketProtocol}, net::SiteForCookies(),
+ net::IsolationInfo(), /*headers=*/{}, network::mojom::kBrowserProcessId,
+ /*render_frame_id=*/0, url::Origin::Create(target_),
+ network::mojom::kWebSocketOptionBlockAllCookies,
+ net::MutableNetworkTrafficAnnotationTag(kTrafficAnnotation),
+ websocket_client_->BindNewHandshakeClientPipe(), mojo::NullRemote(),
+ mojo::NullRemote());
+ FIDO_LOG(DEBUG) << "Creating WebSocket to " << target_.spec();
+ }
+
+ void Write(std::vector<uint8_t> data) override {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(state_, kReady);
+
+ if (!crypter_->Encrypt(&data)) {
+ FIDO_LOG(ERROR) << "Failed to encrypt response";
+ return;
+ }
+ websocket_client_->Write(data);
+ }
+
+ private:
+ enum State {
+ kNone,
+ kConnecting,
+ kConnectingPaired,
+ kConnected,
+ kConnectedPaired,
+ kReady,
+ };
+
+ void OnTunnelReady(
+ bool ok,
+ base::Optional<std::array<uint8_t, device::cablev2::kRoutingIdSize>>
+ routing_id) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(state_ == State::kConnecting || state_ == State::kConnectingPaired);
+
+ if (ok && state_ == State::kConnecting && !routing_id) {
+ FIDO_LOG(ERROR) << "Tunnel server did not specify routing ID";
+ ok = false;
+ }
+
+ if (!ok) {
+ FIDO_LOG(ERROR) << "Failed to connect to tunnel server";
+ read_callback_.Run(base::nullopt);
+ return;
+ }
+
+ FIDO_LOG(DEBUG) << "WebSocket connection established.";
+
+ if (state_ == State::kConnecting) {
+ state_ = State::kConnected;
+ } else {
+ DCHECK_EQ(state_, State::kConnectingPaired);
+ state_ = State::kConnectedPaired;
+ }
+
+ static constexpr std::array<uint8_t, device::cablev2::kRoutingIdSize>
+ kZeroRoutingID = {0, 0, 0};
+ const device::CableEidArray eid =
+ StartAdvertising(routing_id.value_or(kZeroRoutingID));
+ std::vector<uint8_t> msg =
+ handshaker_->BuildInitialMessage(eid, BuildGetInfoResponse());
+ websocket_client_->Write(msg);
+ }
+
+ void OnTunnelData(base::Optional<base::span<const uint8_t>> msg) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (!msg) {
+ FIDO_LOG(DEBUG) << "WebSocket tunnel closed";
+ read_callback_.Run(base::nullopt);
+ return;
+ }
+
+ switch (state_) {
+ case State::kConnectedPaired:
+ case State::kConnected: {
+ base::Optional<std::pair<std::unique_ptr<device::cablev2::Crypter>,
+ device::cablev2::HandshakeHash>>
+ result = handshaker_->ProcessResponse(*msg);
+ handshaker_.reset();
+ if (!result) {
+ FIDO_LOG(ERROR) << "caBLE handshake failure";
+ read_callback_.Run(base::nullopt);
+ return;
+ }
+ FIDO_LOG(DEBUG) << "caBLE handshake complete";
+ crypter_ = std::move(result->first);
+
+ if (state_ == State::kConnected) {
+ std::vector<uint8_t> pairing_data =
+ std::move(generate_pairing_data_)
+ .Run(*peer_identity_, result->second);
+ if (!crypter_->Encrypt(&pairing_data)) {
+ FIDO_LOG(ERROR) << "failed to encode pairing data";
+ return;
+ }
+
+ websocket_client_->Write(pairing_data);
+ }
+
+ state_ = State::kReady;
+ break;
+ }
+
+ case State::kReady: {
+ std::vector<uint8_t> plaintext;
+ if (!crypter_->Decrypt(*msg, &plaintext)) {
+ FIDO_LOG(ERROR) << "failed to decrypt caBLE message";
+ read_callback_.Run(base::nullopt);
+ return;
+ }
+
+ read_callback_.Run(plaintext);
+ break;
+ }
+
+ default:
+ NOTREACHED();
+ }
+ }
+
+ device::CableEidArray StartAdvertising(
+ std::array<uint8_t, device::cablev2::kRoutingIdSize> routing_id) {
+ const device::cablev2::eid::Components components{
+ .tunnel_server_domain = kTunnelServer,
+ .routing_id = routing_id,
+ .nonce = nonce_,
+ };
+ const device::CableEidArray eid_plaintext =
+ device::cablev2::eid::FromComponents(components);
+
+ AES_KEY key;
+ CHECK(AES_set_encrypt_key(eid_key_.data(),
+ /*bits=*/8 * eid_key_.size(), &key) == 0);
+ std::array<uint8_t, AES_BLOCK_SIZE> eid;
+ static_assert(EXTENT(eid_plaintext) == AES_BLOCK_SIZE,
+ "EIDs are not AES blocks");
+ AES_encrypt(/*in=*/eid_plaintext.data(), /*out=*/eid.data(), &key);
+
+ ble_advert_ = platform_->SendBLEAdvert(eid);
+ return eid;
+ }
+
+ Platform* const platform_;
+ State state_ = State::kNone;
+ const std::array<uint8_t, kNonceSize> nonce_;
+ const std::array<uint8_t, kTunnelIdSize> tunnel_id_;
+ const std::array<uint8_t, kEIDKeySize> eid_key_;
+ std::unique_ptr<WebSocketAdapter> websocket_client_;
+ std::unique_ptr<HandshakeInitiator> handshaker_;
+ std::unique_ptr<Crypter> crypter_;
+ network::mojom::NetworkContext* const network_context_;
+ const base::Optional<std::array<uint8_t, kP256X962Length>> peer_identity_;
+ GeneratePairingDataCallback generate_pairing_data_;
+ GURL target_;
+ std::unique_ptr<Platform::BLEAdvert> ble_advert_;
+ base::RepeatingCallback<void(base::Optional<std::vector<uint8_t>>)>
+ read_callback_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+};
+
+class CTAP2Processor : public Transaction {
+ public:
+ CTAP2Processor(std::unique_ptr<Transport> transport,
+ std::unique_ptr<Platform> platform,
+ Transaction::CompleteCallback complete_callback)
+ : transport_(std::move(transport)),
+ platform_(std::move(platform)),
+ complete_callback_(std::move(complete_callback)) {
+ transport_->StartReading(
+ base::BindRepeating(&CTAP2Processor::OnData, base::Unretained(this)));
+ }
+
+ private:
+ void OnData(base::Optional<std::vector<uint8_t>> msg) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (!msg) {
+ FIDO_LOG(ERROR) << "Closing transaction due to transport EOF";
+ std::move(complete_callback_).Run();
+ return;
+ }
+
+ base::Optional<std::vector<uint8_t>> response = ProcessCTAPMessage(*msg);
+ if (!response) {
+ // Fatal error.
+ // TODO: need to signal this to the UI.
+ std::move(complete_callback_).Run();
+ return;
+ }
+
+ if (response->empty()) {
+ // Response is pending.
+ return;
+ }
+
+ transport_->Write(std::move(*response));
+ }
+
+ base::Optional<std::vector<uint8_t>> ProcessCTAPMessage(
+ base::span<const uint8_t> message_bytes) {
+ if (message_bytes.empty()) {
+ return base::nullopt;
+ }
+ const auto command = message_bytes[0];
+ const auto cbor_bytes = message_bytes.subspan(1);
+
+ base::Optional<cbor::Value> payload;
+ if (!cbor_bytes.empty()) {
+ payload = cbor::Reader::Read(cbor_bytes);
+ if (!payload) {
+ FIDO_LOG(ERROR) << "CBOR decoding failed for "
+ << base::HexEncode(cbor_bytes);
+ return base::nullopt;
+ }
+ FIDO_LOG(DEBUG) << "<- (" << base::HexEncode(&command, 1) << ") "
+ << cbor::DiagnosticWriter::Write(*payload);
+ } else {
+ FIDO_LOG(DEBUG) << "<- (" << base::HexEncode(&command, 1)
+ << ") <no payload>";
+ }
+
+ switch (command) {
+ case static_cast<uint8_t>(
+ device::CtapRequestCommand::kAuthenticatorGetInfo): {
+ if (payload) {
+ FIDO_LOG(ERROR) << "getInfo command incorrectly contained payload";
+ return base::nullopt;
+ }
+
+ base::Optional<std::vector<uint8_t>> response = BuildGetInfoResponse();
+ if (!response) {
+ return base::nullopt;
+ }
+ response->insert(
+ response->begin(),
+ static_cast<uint8_t>(CtapDeviceResponseCode::kSuccess));
+ return response;
+ }
+
+ case static_cast<uint8_t>(
+ device::CtapRequestCommand::kAuthenticatorMakeCredential): {
+ if (!payload || !payload->is_map()) {
+ FIDO_LOG(ERROR) << "Invalid makeCredential payload";
+ return base::nullopt;
+ }
+
+ MakeCredRequest make_cred_request;
+ if (!device::cbor_extract::Extract<MakeCredRequest>(
+ &make_cred_request, kMakeCredParseSteps, payload->GetMap())) {
+ FIDO_LOG(ERROR) << "Failed to parse makeCredential request";
+ return base::nullopt;
+ }
+
+ std::vector<int> algorithms;
+ if (!device::cbor_extract::ForEachPublicKeyEntry(
+ *make_cred_request.cred_params, cbor::Value("alg"),
+ base::BindRepeating(
+ [](std::vector<int>* out,
+ const cbor::Value& value) -> bool {
+ if (!value.is_integer()) {
+ return false;
+ }
+ const int64_t alg = value.GetInteger();
+
+ if (alg > std::numeric_limits<int>::max() ||
+ alg < std::numeric_limits<int>::min()) {
+ return false;
+ }
+ out->push_back(static_cast<int>(alg));
+ return true;
+ },
+ base::Unretained(&algorithms)))) {
+ return base::nullopt;
+ }
+
+ std::vector<std::vector<uint8_t>> excluded_credential_ids;
+ if (make_cred_request.excluded_credentials &&
+ !device::cbor_extract::ForEachPublicKeyEntry(
+ *make_cred_request.excluded_credentials, cbor::Value("id"),
+ base::BindRepeating(
+ [](std::vector<std::vector<uint8_t>>* out,
+ const cbor::Value& value) -> bool {
+ if (!value.is_bytestring()) {
+ return false;
+ }
+ out->push_back(value.GetBytestring());
+ return true;
+ },
+ base::Unretained(&excluded_credential_ids)))) {
+ return base::nullopt;
+ }
+
+ // TODO: plumb the rk flag through once GmsCore supports resident
+ // keys. This will require support for optional maps in |Extract|.
+ platform_->MakeCredential(
+ *make_cred_request.origin, *make_cred_request.rp_id,
+ *make_cred_request.challenge, *make_cred_request.user_id,
+ algorithms, excluded_credential_ids,
+ /*resident_key_required=*/false,
+ base::BindOnce(&CTAP2Processor::OnMakeCredentialResponse,
+ weak_factory_.GetWeakPtr()));
+ return std::vector<uint8_t>();
+ }
+
+ case static_cast<uint8_t>(
+ device::CtapRequestCommand::kAuthenticatorGetAssertion): {
+ if (!payload || !payload->is_map()) {
+ FIDO_LOG(ERROR) << "Invalid makeCredential payload";
+ return base::nullopt;
+ }
+ GetAssertionRequest get_assertion_request;
+ if (!device::cbor_extract::Extract<GetAssertionRequest>(
+ &get_assertion_request, kGetAssertionParseSteps,
+ payload->GetMap())) {
+ FIDO_LOG(ERROR) << "Failed to parse getAssertion request";
+ return base::nullopt;
+ }
+
+ std::vector<std::vector<uint8_t>> allowed_credential_ids;
+ if (get_assertion_request.allowed_credentials &&
+ !device::cbor_extract::ForEachPublicKeyEntry(
+ *get_assertion_request.allowed_credentials, cbor::Value("id"),
+ base::BindRepeating(
+ [](std::vector<std::vector<uint8_t>>* out,
+ const cbor::Value& value) -> bool {
+ if (!value.is_bytestring()) {
+ return false;
+ }
+ out->push_back(value.GetBytestring());
+ return true;
+ },
+ base::Unretained(&allowed_credential_ids)))) {
+ return base::nullopt;
+ }
+
+ platform_->GetAssertion(
+ *get_assertion_request.origin, *get_assertion_request.rp_id,
+ *get_assertion_request.challenge, allowed_credential_ids,
+ base::BindOnce(&CTAP2Processor::OnGetAssertionResponse,
+ weak_factory_.GetWeakPtr()));
+
+ return std::vector<uint8_t>();
+ }
+
+ default:
+ FIDO_LOG(ERROR) << "Received unknown command "
+ << static_cast<unsigned>(command);
+ return base::nullopt;
+ }
+ }
+
+ void OnMakeCredentialResponse(uint32_t ctap_status,
+ base::span<const uint8_t> client_data_json,
+ base::span<const uint8_t> attestation_object) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_LE(ctap_status, 0xFFu);
+
+ std::vector<uint8_t> response = {base::checked_cast<uint8_t>(ctap_status)};
+ if (ctap_status == static_cast<uint8_t>(CtapDeviceResponseCode::kSuccess)) {
+ // TODO: pass response parameters from the Java side.
+ base::Optional<cbor::Value> cbor_attestation_object =
+ cbor::Reader::Read(attestation_object);
+ if (!cbor_attestation_object || !cbor_attestation_object->is_map()) {
+ FIDO_LOG(ERROR) << "invalid CBOR attestation object";
+ return;
+ }
+
+ AttestationObject attestation_object;
+ if (!device::cbor_extract::Extract<AttestationObject>(
+ &attestation_object, kAttObjParseSteps,
+ cbor_attestation_object->GetMap())) {
+ FIDO_LOG(ERROR) << "attestation object parse failed";
+ return;
+ }
+
+ cbor::Value::MapValue response_map;
+ response_map.emplace(1, base::StringPiece(*attestation_object.fmt));
+ response_map.emplace(
+ 2, base::span<const uint8_t>(*attestation_object.auth_data));
+ response_map.emplace(3, attestation_object.statement->Clone());
+ response_map.emplace(device::kAndroidClientDataExtOutputKey,
+ client_data_json);
+
+ base::Optional<std::vector<uint8_t>> response_payload =
+ cbor::Writer::Write(cbor::Value(std::move(response_map)));
+ if (!response_payload) {
+ return;
+ }
+ response.insert(response.end(), response_payload->begin(),
+ response_payload->end());
+ }
+
+ transport_->Write(std::move(response));
+ }
+
+ void OnGetAssertionResponse(uint32_t ctap_status,
+ base::span<const uint8_t> client_data_json,
+ base::span<const uint8_t> credential_id,
+ base::span<const uint8_t> authenticator_data,
+ base::span<const uint8_t> signature) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_LE(ctap_status, 0xFFu);
+ std::vector<uint8_t> response = {base::checked_cast<uint8_t>(ctap_status)};
+
+ if (ctap_status == static_cast<uint8_t>(CtapDeviceResponseCode::kSuccess)) {
+ cbor::Value::MapValue credential_descriptor;
+ credential_descriptor.emplace("type", device::kPublicKey);
+ credential_descriptor.emplace("id", credential_id);
+ cbor::Value::ArrayValue transports;
+ transports.emplace_back("internal");
+ transports.emplace_back("cable");
+ credential_descriptor.emplace("transports", std::move(transports));
+ cbor::Value::MapValue response_map;
+ response_map.emplace(1, std::move(credential_descriptor));
+ response_map.emplace(2, authenticator_data);
+ response_map.emplace(3, signature);
+ // TODO: add user entity to support resident keys.
+ response_map.emplace(device::kAndroidClientDataExtOutputKey,
+ client_data_json);
+
+ base::Optional<std::vector<uint8_t>> response_payload =
+ cbor::Writer::Write(cbor::Value(std::move(response_map)));
+ if (!response_payload) {
+ return;
+ }
+ response.insert(response.end(), response_payload->begin(),
+ response_payload->end());
+ }
+
+ transport_->Write(std::move(response));
+ }
+
+ const std::unique_ptr<Transport> transport_;
+ const std::unique_ptr<Platform> platform_;
+ Transaction::CompleteCallback complete_callback_;
+ SEQUENCE_CHECKER(sequence_checker_);
+ base::WeakPtrFactory<CTAP2Processor> weak_factory_{this};
+};
+
+static bssl::UniquePtr<EC_KEY> IdentityKey(
+ base::span<const uint8_t, 32> root_secret) {
+ std::array<uint8_t, 32> seed;
+ seed = device::cablev2::Derive<EXTENT(seed)>(
+ root_secret, /*nonce=*/base::span<uint8_t>(),
+ device::cablev2::DerivedValueType::kIdentityKeySeed);
+ bssl::UniquePtr<EC_GROUP> p256(
+ EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
+ return bssl::UniquePtr<EC_KEY>(
+ EC_KEY_derive_from_secret(p256.get(), seed.data(), seed.size()));
+}
+
+class PairingDataGenerator {
+ public:
+ static base::OnceCallback<
+ std::vector<uint8_t>(base::span<const uint8_t, device::kP256X962Length>,
+ device::cablev2::HandshakeHash)>
+ GetClosure(base::span<const uint8_t, kRootSecretSize> root_secret,
+ const std::string& name,
+ base::Optional<std::vector<uint8_t>> contact_id) {
+ auto* generator =
+ new PairingDataGenerator(root_secret, name, std::move(contact_id));
+ return base::BindOnce(&PairingDataGenerator::Generate,
+ base::Owned(generator));
+ }
+
+ private:
+ PairingDataGenerator(base::span<const uint8_t, kRootSecretSize> root_secret,
+ const std::string& name,
+ base::Optional<std::vector<uint8_t>> contact_id)
+ : root_secret_(fido_parsing_utils::Materialize(root_secret)),
+ name_(name),
+ contact_id_(std::move(contact_id)) {}
+
+ std::vector<uint8_t> Generate(
+ base::span<const uint8_t, device::kP256X962Length> peer_public_key_x962,
+ device::cablev2::HandshakeHash handshake_hash) {
+ cbor::Value::MapValue map;
+
+ if (contact_id_) {
+ map.emplace(1, std::move(*contact_id_));
+
+ std::array<uint8_t, device::cablev2::kNonceSize> pairing_id;
+ crypto::RandBytes(pairing_id);
+
+ map.emplace(2, pairing_id);
+
+ std::array<uint8_t, 32> paired_secret;
+ paired_secret = device::cablev2::Derive<EXTENT(paired_secret)>(
+ root_secret_, pairing_id,
+ device::cablev2::DerivedValueType::kPairedSecret);
+
+ map.emplace(3, paired_secret);
+
+ bssl::UniquePtr<EC_KEY> identity_key(IdentityKey(root_secret_));
+ device::CableAuthenticatorIdentityKey public_key;
+ CHECK_EQ(
+ public_key.size(),
+ EC_POINT_point2oct(EC_KEY_get0_group(identity_key.get()),
+ EC_KEY_get0_public_key(identity_key.get()),
+ POINT_CONVERSION_UNCOMPRESSED, public_key.data(),
+ public_key.size(), /*ctx=*/nullptr));
+
+ map.emplace(4, public_key);
+ map.emplace(5, name_);
+
+ map.emplace(
+ 6, device::cablev2::CalculatePairingSignature(
+ identity_key.get(), peer_public_key_x962, handshake_hash));
+ }
+
+ std::vector<uint8_t> empty_vector;
+ return device::cablev2::EncodePaddedCBORMap(std::move(map))
+ .value_or(empty_vector);
+ }
+
+ const std::array<uint8_t, kRootSecretSize> root_secret_;
+ const std::string name_;
+ base::Optional<std::vector<uint8_t>> contact_id_;
+};
+
+} // namespace
+
+Platform::BLEAdvert::~BLEAdvert() = default;
+Platform::~Platform() = default;
+Transport::~Transport() = default;
+Transaction::~Transaction() = default;
+
+std::unique_ptr<Transaction> TransactWithPlaintextTransport(
+ std::unique_ptr<Platform> platform,
+ std::unique_ptr<Transport> transport,
+ Transaction::CompleteCallback complete_callback) {
+ return std::make_unique<CTAP2Processor>(
+ std::move(transport), std::move(platform), std::move(complete_callback));
+}
+
+std::unique_ptr<Transaction> TransactFromQRCode(
+ std::unique_ptr<Platform> platform,
+ network::mojom::NetworkContext* network_context,
+ base::span<const uint8_t, kRootSecretSize> root_secret,
+ const std::string& authenticator_name,
+ base::span<const uint8_t, 16> qr_secret,
+ base::span<const uint8_t, kP256X962Length> peer_identity,
+ base::Optional<std::vector<uint8_t>> contact_id,
+ Transaction::CompleteCallback complete_callback) {
+ auto generate_pairing_data = PairingDataGenerator::GetClosure(
+ root_secret, authenticator_name, contact_id);
+
+ Platform* const platform_ptr = platform.get();
+ return std::make_unique<CTAP2Processor>(
+ std::make_unique<TunnelTransport>(platform_ptr, network_context,
+ qr_secret, peer_identity,
+ std::move(generate_pairing_data)),
+ std::move(platform), std::move(complete_callback));
+}
+
+std::unique_ptr<Transaction> TransactFromFCM(
+ std::unique_ptr<Platform> platform,
+ network::mojom::NetworkContext* network_context,
+ base::span<const uint8_t, kRootSecretSize> root_secret,
+ std::array<uint8_t, kRoutingIdSize> routing_id,
+ base::span<const uint8_t, kTunnelIdSize> tunnel_id,
+ base::span<const uint8_t> pairing_id,
+ base::span<const uint8_t, kClientNonceSize> client_nonce,
+ Transaction::CompleteCallback complete_callback) {
+ std::array<uint8_t, 32> paired_secret;
+ paired_secret = Derive<EXTENT(paired_secret)>(
+ root_secret, pairing_id, DerivedValueType::kPairedSecret);
+
+ Platform* const platform_ptr = platform.get();
+ return std::make_unique<CTAP2Processor>(
+ std::make_unique<TunnelTransport>(platform_ptr, network_context,
+ paired_secret, client_nonce, routing_id,
+ tunnel_id, IdentityKey(root_secret)),
+ std::move(platform), std::move(complete_callback));
+}
+
+} // namespace authenticator
+} // namespace cablev2
+} // namespace device
diff --git a/chromium/device/fido/cable/v2_authenticator.h b/chromium/device/fido/cable/v2_authenticator.h
new file mode 100644
index 00000000000..28810230582
--- /dev/null
+++ b/chromium/device/fido/cable/v2_authenticator.h
@@ -0,0 +1,128 @@
+// 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_CABLE_V2_AUTHENTICATOR_H_
+#define DEVICE_FIDO_CABLE_V2_AUTHENTICATOR_H_
+
+#include <string>
+#include <vector>
+
+#include <stdint.h>
+
+#include "base/callback_forward.h"
+#include "base/containers/span.h"
+#include "base/optional.h"
+#include "device/fido/cable/v2_constants.h"
+#include "device/fido/fido_constants.h"
+#include "services/network/public/mojom/network_context.mojom-forward.h"
+
+namespace device {
+namespace cablev2 {
+namespace authenticator {
+
+// Platform abstracts the actions taken by the platform, i.e. the
+// credential-store operations themselves, plus an interface for BLE
+// advertising.
+class Platform {
+ public:
+ // BLEAdvert represents a currently-transmitting advert. Destroying the object
+ // stops the transmission.
+ class BLEAdvert {
+ public:
+ virtual ~BLEAdvert();
+ };
+
+ virtual ~Platform();
+
+ using MakeCredentialCallback =
+ base::OnceCallback<void(uint32_t status,
+ base::span<const uint8_t> client_data_json,
+ base::span<const uint8_t> attestation_obj)>;
+ using GetAssertionCallback =
+ base::OnceCallback<void(uint32_t status,
+ base::span<const uint8_t> client_data_json,
+ base::span<const uint8_t> cred_id,
+ base::span<const uint8_t> auth_data,
+ base::span<const uint8_t> sig)>;
+
+ virtual void MakeCredential(
+ const std::string& origin,
+ const std::string& rp_id,
+ base::span<const uint8_t> challenge,
+ base::span<const uint8_t> user_id,
+ base::span<const int> algorithms,
+ base::span<const std::vector<uint8_t>> excluded_cred_ids,
+ bool resident_key_required,
+ MakeCredentialCallback callback) = 0;
+
+ virtual void GetAssertion(
+ const std::string& origin,
+ const std::string& rp_id,
+ base::span<const uint8_t> challenge,
+ base::span<const std::vector<uint8_t>> allowed_cred_ids,
+ GetAssertionCallback callback) = 0;
+
+ virtual std::unique_ptr<BLEAdvert> SendBLEAdvert(
+ base::span<uint8_t, 16> payload) = 0;
+};
+
+// Transport abstracts a way of transmitting to, and receiving from, the peer.
+// The framing of messages must be preserved.
+class Transport {
+ public:
+ virtual ~Transport();
+
+ // StartReading requests that the given callback be called whenever a message
+ // arrives from the peer.
+ virtual void StartReading(
+ base::RepeatingCallback<void(base::Optional<std::vector<uint8_t>>)>
+ read_callback) = 0;
+ virtual void Write(std::vector<uint8_t> data) = 0;
+};
+
+// A Transaction is a handle to an ongoing caBLEv2 transaction with a peer.
+class Transaction {
+ public:
+ using CompleteCallback = base::OnceCallback<void()>;
+
+ virtual ~Transaction();
+};
+
+// TransactWithPlaintextTransport allows an arbitrary transport to be used for a
+// caBLEv2 transaction.
+std::unique_ptr<Transaction> TransactWithPlaintextTransport(
+ std::unique_ptr<Platform> platform,
+ std::unique_ptr<Transport> transport,
+ Transaction::CompleteCallback complete_callback);
+
+// TransactFromQRCode starts a network-based transaction based on the decoded
+// contents of a QR code.
+std::unique_ptr<Transaction> TransactFromQRCode(
+ std::unique_ptr<Platform> platform,
+ network::mojom::NetworkContext* network_context,
+ base::span<const uint8_t, kRootSecretSize> root_secret,
+ const std::string& authenticator_name,
+ // TODO: name this constant.
+ base::span<const uint8_t, 16> qr_secret,
+ base::span<const uint8_t, kP256X962Length> peer_identity,
+ base::Optional<std::vector<uint8_t>> contact_id,
+ Transaction::CompleteCallback complete_callback);
+
+// TransactFromQRCode starts a network-based transaction based on the decoded
+// contents of a cloud message.
+std::unique_ptr<Transaction> TransactFromFCM(
+ std::unique_ptr<Platform> platform,
+ network::mojom::NetworkContext* network_context,
+ base::span<const uint8_t, kRootSecretSize> root_secret,
+ std::array<uint8_t, kRoutingIdSize> routing_id,
+ base::span<const uint8_t, kTunnelIdSize> tunnel_id,
+ base::span<const uint8_t> pairing_id,
+ base::span<const uint8_t, kClientNonceSize> client_nonce,
+ Transaction::CompleteCallback complete_callback);
+
+} // namespace authenticator
+} // namespace cablev2
+} // namespace device
+
+#endif // DEVICE_FIDO_CABLE_V2_AUTHENTICATOR_H_
diff --git a/chromium/device/fido/cable/v2_constants.h b/chromium/device/fido/cable/v2_constants.h
new file mode 100644
index 00000000000..3d2dddf94b2
--- /dev/null
+++ b/chromium/device/fido/cable/v2_constants.h
@@ -0,0 +1,44 @@
+// 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_CABLE_V2_CONSTANTS_H_
+#define DEVICE_FIDO_CABLE_V2_CONSTANTS_H_
+
+namespace device {
+namespace cablev2 {
+
+// kNonceSize is the number of bytes of nonce in the BLE advert.
+constexpr size_t kNonceSize = 8;
+// kClientNonceSize is the number of bytes of nonce sent by the client, via the
+// tunnel server, for a pairing-based handshake.
+constexpr size_t kClientNonceSize = 16;
+// kRoutingIdSize is the number of bytes of routing information in the BLE
+// advert.
+constexpr size_t kRoutingIdSize = 3;
+// kTunnelIdSize is the number of bytes of opaque tunnel ID, used to identify a
+// specific tunnel to the tunnel service.
+constexpr size_t kTunnelIdSize = 16;
+// kEIDKeySize is the size of the AES key used to encrypt BLE adverts.
+constexpr size_t kEIDKeySize = 32;
+// kPSKSize is the size of the Noise pre-shared key used in handshakes.
+constexpr size_t kPSKSize = 32;
+// kRootSecretSize is the size of the main key maintained by authenticators.
+constexpr size_t kRootSecretSize = 32;
+// kQRKeySize is the size of the private key data that generates a QR code. It
+// consists of a 256-bit seed value that's used to genertate the P-256 private
+// key and a 128-bit secret.
+constexpr size_t kQRSecretSize = 16;
+constexpr size_t kQRSeedSize = 32;
+constexpr size_t kQRKeySize = kQRSeedSize + kQRSecretSize;
+// kCompressedPublicKeySize is the size of a compressed X9.62 public key.
+constexpr size_t kCompressedPublicKeySize =
+ /* type byte */ 1 + /* field element */ (256 / 8);
+// kQRDataSize is the size of the (unencoded) QR payload. It's a compressed
+// public key followed by the QR secret.
+constexpr size_t kQRDataSize = kCompressedPublicKeySize + kQRSecretSize;
+
+} // namespace cablev2
+} // namespace device
+
+#endif // DEVICE_FIDO_CABLE_V2_CONSTANTS_H_
diff --git a/chromium/device/fido/cable/v2_discovery.cc b/chromium/device/fido/cable/v2_discovery.cc
new file mode 100644
index 00000000000..794f06f8370
--- /dev/null
+++ b/chromium/device/fido/cable/v2_discovery.cc
@@ -0,0 +1,119 @@
+// 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/v2_discovery.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/strings/string_number_conversions.h"
+#include "components/device_event_log/device_event_log.h"
+#include "device/fido/cable/fido_tunnel_device.h"
+#include "device/fido/cable/v2_handshake.h"
+#include "device/fido/fido_parsing_utils.h"
+#include "third_party/boringssl/src/include/openssl/aes.h"
+
+namespace device {
+namespace cablev2 {
+
+Discovery::Discovery(
+ network::mojom::NetworkContext* network_context,
+ base::span<const uint8_t, kQRKeySize> qr_generator_key,
+ std::vector<std::unique_ptr<Pairing>> pairings,
+ base::Optional<base::RepeatingCallback<void(std::unique_ptr<Pairing>)>>
+ pairing_callback)
+ : FidoDeviceDiscovery(
+ FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy),
+ network_context_(network_context),
+ local_identity_seed_(fido_parsing_utils::Materialize(
+ base::span<const uint8_t, kQRSeedSize>(qr_generator_key.data(),
+ kQRSeedSize))),
+ qr_secret_(fido_parsing_utils::Materialize(
+ base::span<const uint8_t, kQRSecretSize>(
+ qr_generator_key.data() + kQRSeedSize,
+ kQRSecretSize))),
+ eid_key_(Derive<EXTENT(eid_key_)>(qr_secret_,
+ base::span<const uint8_t>(),
+ DerivedValueType::kEIDKey)),
+ pairings_(std::move(pairings)),
+ pairing_callback_(std::move(pairing_callback)) {
+ static_assert(EXTENT(qr_generator_key) == kQRSecretSize + kQRSeedSize, "");
+}
+
+Discovery::~Discovery() = default;
+
+void Discovery::StartInternal() {
+ DCHECK(!started_);
+
+ for (auto& pairing : pairings_) {
+ tunnels_pending_advert_.emplace_back(std::make_unique<FidoTunnelDevice>(
+ network_context_, std::move(pairing)));
+ }
+ pairings_.clear();
+
+ started_ = true;
+ NotifyDiscoveryStarted(true);
+
+ std::vector<CableEidArray> pending_eids(std::move(pending_eids_));
+ for (const auto& eid : pending_eids) {
+ OnBLEAdvertSeen("", eid);
+ }
+}
+
+void Discovery::OnBLEAdvertSeen(const std::string& address,
+ const CableEidArray& eid) {
+ if (!started_) {
+ pending_eids_.push_back(eid);
+ return;
+ }
+
+ if (base::Contains(observed_eids_, eid)) {
+ return;
+ }
+ observed_eids_.insert(eid);
+
+ // Check whether the EID satisfies any pending tunnels.
+ for (std::vector<std::unique_ptr<FidoTunnelDevice>>::iterator i =
+ tunnels_pending_advert_.begin();
+ i != tunnels_pending_advert_.end(); i++) {
+ if (!(*i)->MatchEID(eid)) {
+ continue;
+ }
+
+ FIDO_LOG(DEBUG) << " (" << base::HexEncode(eid)
+ << " matches pending tunnel)";
+ std::unique_ptr<FidoTunnelDevice> device(std::move(*i));
+ tunnels_pending_advert_.erase(i);
+ AddDevice(std::move(device));
+ return;
+ }
+
+ // Check whether the EID matches a QR code.
+ AES_KEY aes_key;
+ CHECK(AES_set_decrypt_key(eid_key_.data(),
+ /*bits=*/8 * eid_key_.size(), &aes_key) == 0);
+ CableEidArray plaintext;
+ static_assert(EXTENT(plaintext) == AES_BLOCK_SIZE, "EIDs are not AES blocks");
+ AES_decrypt(/*in=*/eid.data(), /*out=*/plaintext.data(), &aes_key);
+ if (cablev2::eid::IsValid(plaintext)) {
+ FIDO_LOG(DEBUG) << " (" << base::HexEncode(eid) << " matches QR code)";
+ AddDevice(std::make_unique<cablev2::FidoTunnelDevice>(
+ network_context_,
+ base::BindOnce(&Discovery::AddPairing, weak_factory_.GetWeakPtr()),
+ qr_secret_, local_identity_seed_, eid, plaintext));
+ return;
+ }
+
+ FIDO_LOG(DEBUG) << " (" << base::HexEncode(eid) << ": no v2 match)";
+}
+
+void Discovery::AddPairing(std::unique_ptr<Pairing> pairing) {
+ if (!pairing_callback_) {
+ return;
+ }
+
+ pairing_callback_->Run(std::move(pairing));
+}
+
+} // namespace cablev2
+} // namespace device
diff --git a/chromium/device/fido/cable/v2_discovery.h b/chromium/device/fido/cable/v2_discovery.h
new file mode 100644
index 00000000000..a3607cd7545
--- /dev/null
+++ b/chromium/device/fido/cable/v2_discovery.h
@@ -0,0 +1,74 @@
+// 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_CABLE_V2_DISCOVERY_H_
+#define DEVICE_FIDO_CABLE_V2_DISCOVERY_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/component_export.h"
+#include "base/containers/flat_set.h"
+#include "base/containers/span.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "device/fido/cable/cable_discovery_data.h"
+#include "device/fido/cable/v2_constants.h"
+#include "device/fido/fido_device_discovery.h"
+#include "services/network/public/mojom/network_context.mojom-forward.h"
+
+namespace device {
+namespace cablev2 {
+
+struct Pairing;
+class FidoTunnelDevice;
+
+// Discovery creates caBLEv2 devices, either based on |pairings|, or when a BLE
+// advert is seen that matches |qr_generator_key|. It does not actively scan for
+// BLE adverts itself. Rather it depends on |OnBLEAdvertSeen| getting called.
+class COMPONENT_EXPORT(DEVICE_FIDO) Discovery
+ : public FidoDeviceDiscovery,
+ public FidoDeviceDiscovery::BLEObserver {
+ public:
+ Discovery(
+ network::mojom::NetworkContext* network_context,
+ base::span<const uint8_t, kQRKeySize> qr_generator_key,
+ std::vector<std::unique_ptr<Pairing>> pairings,
+ // pairing_callback will be called when a QR-initiated connection receives
+ // pairing information from the peer.
+ base::Optional<base::RepeatingCallback<void(std::unique_ptr<Pairing>)>>
+ pairing_callback);
+ ~Discovery() override;
+ Discovery(const Discovery&) = delete;
+ Discovery& operator=(const Discovery&) = delete;
+
+ // FidoDeviceDiscovery:
+ void StartInternal() override;
+
+ // BLEObserver:
+ void OnBLEAdvertSeen(const std::string& address,
+ const CableEidArray& eid) override;
+
+ private:
+ void AddPairing(std::unique_ptr<Pairing> pairing);
+
+ network::mojom::NetworkContext* const network_context_;
+ const std::array<uint8_t, kQRSeedSize> local_identity_seed_;
+ const std::array<uint8_t, kQRSecretSize> qr_secret_;
+ const std::array<uint8_t, kEIDKeySize> eid_key_;
+ std::vector<std::unique_ptr<Pairing>> pairings_;
+ const base::Optional<base::RepeatingCallback<void(std::unique_ptr<Pairing>)>>
+ pairing_callback_;
+ std::vector<std::unique_ptr<FidoTunnelDevice>> tunnels_pending_advert_;
+ base::flat_set<CableEidArray> observed_eids_;
+ bool started_ = false;
+ std::vector<CableEidArray> pending_eids_;
+ base::WeakPtrFactory<Discovery> weak_factory_{this};
+};
+
+} // namespace cablev2
+} // namespace device
+
+#endif // DEVICE_FIDO_CABLE_V2_DISCOVERY_H_
diff --git a/chromium/device/fido/cable/v2_handshake.cc b/chromium/device/fido/cable/v2_handshake.cc
index 41c12e9e66a..b2be4ffb7bd 100644
--- a/chromium/device/fido/cable/v2_handshake.cc
+++ b/chromium/device/fido/cable/v2_handshake.cc
@@ -7,9 +7,11 @@
#include <array>
#include <type_traits>
+#include "base/base64url.h"
#include "base/bits.h"
#include "base/numerics/safe_math.h"
#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
#include "components/cbor/reader.h"
#include "components/cbor/values.h"
#include "components/cbor/writer.h"
@@ -23,10 +25,15 @@
#include "third_party/boringssl/src/include/openssl/ec_key.h"
#include "third_party/boringssl/src/include/openssl/ecdh.h"
#include "third_party/boringssl/src/include/openssl/hkdf.h"
+#include "third_party/boringssl/src/include/openssl/hmac.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/sha.h"
#include "url/gurl.h"
+namespace device {
+namespace cablev2 {
+
namespace {
// Maximum value of a sequence number. Exceeding this causes all operations to
@@ -47,16 +54,39 @@ bool ConstructNonce(uint32_t counter, base::span<uint8_t, 12> out_nonce) {
return true;
}
-} // namespace
+std::array<uint8_t, 32> PairingSignature(
+ const EC_KEY* identity_key,
+ base::span<const uint8_t, kP256X962Length> peer_public_key_x962,
+ base::span<const uint8_t, std::tuple_size<HandshakeHash>::value>
+ handshake_hash) {
+ const EC_GROUP* const p256 = EC_KEY_get0_group(identity_key);
+ bssl::UniquePtr<EC_POINT> peer_public_key(EC_POINT_new(p256));
+ CHECK(EC_POINT_oct2point(p256, peer_public_key.get(),
+ peer_public_key_x962.data(),
+ peer_public_key_x962.size(),
+ /*ctx=*/nullptr));
+ uint8_t shared_secret[32];
+ CHECK(ECDH_compute_key(shared_secret, sizeof(shared_secret),
+ peer_public_key.get(), identity_key,
+ /*kdf=*/nullptr) == sizeof(shared_secret));
+
+ std::array<uint8_t, SHA256_DIGEST_LENGTH> expected_signature;
+ unsigned expected_signature_len = 0;
+ CHECK(HMAC(EVP_sha256(), /*key=*/shared_secret, sizeof(shared_secret),
+ handshake_hash.data(), handshake_hash.size(),
+ expected_signature.data(), &expected_signature_len) != nullptr);
+ CHECK_EQ(expected_signature_len, EXTENT(expected_signature));
+ return expected_signature;
+}
-namespace device {
-namespace cablev2 {
+} // namespace
namespace tunnelserver {
-GURL GetURL(uint32_t domain, Action action, base::span<const uint8_t, 16> id) {
- std::string ret = "wss://";
+std::string DecodeDomain(uint32_t domain) {
static const char kBase32Chars[33] = "abcdefghijklmnopqrstuvwxyz234567";
+
+ std::string ret;
ret.push_back(kBase32Chars[(domain >> 17) & 0x1f]);
ret.push_back(kBase32Chars[(domain >> 12) & 0x1f]);
ret.push_back(kBase32Chars[(domain >> 7) & 0x1f]);
@@ -66,72 +96,113 @@ GURL GetURL(uint32_t domain, Action action, base::span<const uint8_t, 16> id) {
static const char kTLDs[4][5] = {"com", "org", "net", "info"};
ret += kTLDs[domain & 3];
- switch (action) {
- case Action::kNew:
- ret += "/cable/new/";
- break;
- case Action::kConnect:
- ret += "/cable/connect/";
- break;
- }
+ return ret;
+}
+
+GURL GetNewTunnelURL(uint32_t domain, base::span<const uint8_t, 16> id) {
+ std::string ret = "wss://" + DecodeDomain(domain) + "/cable/new/";
+
+ ret += base::HexEncode(id);
+ const GURL url(ret);
+ DCHECK(url.is_valid());
+ return url;
+}
+GURL GetConnectURL(uint32_t domain,
+ std::array<uint8_t, kRoutingIdSize> routing_id,
+ base::span<const uint8_t, 16> id) {
+ std::string ret = "wss://" + DecodeDomain(domain) + "/cable/connect/";
+
+ ret += base::HexEncode(routing_id);
+ ret += "/";
ret += base::HexEncode(id);
+
const GURL url(ret);
DCHECK(url.is_valid());
return url;
}
+
+GURL GetContactURL(const std::string& tunnel_server,
+ base::span<const uint8_t> contact_id) {
+ std::string contact_id_base64;
+ base::Base64UrlEncode(
+ base::StringPiece(reinterpret_cast<const char*>(contact_id.data()),
+ contact_id.size()),
+ base::Base64UrlEncodePolicy::OMIT_PADDING, &contact_id_base64);
+ GURL ret(std::string("wss://") + tunnel_server + "/cable/contact/" +
+ contact_id_base64);
+ DCHECK(ret.is_valid());
+ return ret;
+}
+
} // namespace tunnelserver
namespace eid {
CableEidArray FromComponents(const Components& components) {
DCHECK_EQ(components.tunnel_server_domain >> 22, 0u);
- DCHECK_EQ(components.shard_id >> 6, 0);
- const uint32_t header = components.tunnel_server_domain |
- (static_cast<uint32_t>(components.shard_id) << 22);
CableEidArray eid;
- constexpr size_t eid_size =
- std::tuple_size<std::remove_reference<decltype(eid)>::type>::value;
- memset(eid.data(), 0, eid.size());
- static_assert(eid_size >= sizeof(header), "EID too small");
- memcpy(eid.data(), &header, sizeof(header));
- static_assert(eid_size == 6 + kNonceSize, "EID wrong size");
- static_assert(
- std::tuple_size<decltype(components.nonce)>::value == kNonceSize,
- "Nonce wrong size");
- memcpy(eid.data() + 6, components.nonce.data(), kNonceSize);
+ static_assert(EXTENT(eid) == 16, "");
+ eid[0] = components.tunnel_server_domain;
+ eid[1] = components.tunnel_server_domain >> 8;
+ eid[2] = components.tunnel_server_domain >> 16;
+ static_assert(EXTENT(components.routing_id) == 3, "");
+ eid[3] = components.routing_id[0];
+ eid[4] = components.routing_id[1];
+ eid[5] = components.routing_id[2];
+ eid[6] = 0;
+ eid[7] = 0;
+ static_assert(EXTENT(eid) == 8 + kNonceSize, "");
+ memcpy(&eid[8], components.nonce.data(), kNonceSize);
+
return eid;
}
bool IsValid(const CableEidArray& eid) {
- static_assert(
- std::tuple_size<std::remove_reference<decltype(eid)>::type>::value >= 6,
- "EID too small");
- return (eid[3] & 0xc0) == 0 && eid[4] == 0 && eid[5] == 0;
+ static_assert(EXTENT(eid) >= 8, "");
+ return eid[6] == 0 && eid[7] == 0 && (eid[2] >> 6) == 0;
}
Components ToComponents(const CableEidArray& eid) {
DCHECK(IsValid(eid));
- constexpr size_t eid_size =
- std::tuple_size<std::remove_reference<decltype(eid)>::type>::value;
Components ret;
- uint32_t header;
- static_assert(eid_size >= sizeof(header), "EID too small");
- memcpy(&header, eid.data(), sizeof(header));
- ret.shard_id = (header >> 22) & 0x3f;
- ret.tunnel_server_domain = header & 0x3fffff;
- static_assert(eid_size == 6 + std::tuple_size<decltype(ret.nonce)>::value,
- "EID too small");
- memcpy(ret.nonce.data(), eid.data() + 6, ret.nonce.size());
+ static_assert(EXTENT(eid) == 16, "");
+ ret.tunnel_server_domain = static_cast<uint32_t>(eid[0]) |
+ (static_cast<uint32_t>(eid[1]) << 8) |
+ ((static_cast<uint32_t>(eid[2]) & 0x3f) << 16);
+ ret.routing_id[0] = eid[3];
+ ret.routing_id[1] = eid[4];
+ ret.routing_id[2] = eid[5];
+
+ static_assert(EXTENT(eid) == 8 + kNonceSize, "");
+ memcpy(ret.nonce.data(), &eid[8], kNonceSize);
return ret;
}
} // namespace eid
+namespace internal {
+
+void Derive(uint8_t* out,
+ size_t out_len,
+ base::span<const uint8_t> secret,
+ base::span<const uint8_t> nonce,
+ DerivedValueType type) {
+ static_assert(sizeof(DerivedValueType) <= sizeof(uint32_t), "");
+ const uint32_t type32 = static_cast<uint32_t>(type);
+
+ HKDF(out, out_len, EVP_sha256(), secret.data(), secret.size(),
+ /*salt=*/nonce.data(), nonce.size(),
+ /*info=*/reinterpret_cast<const uint8_t*>(&type32), sizeof(type32));
+}
+
+} // namespace internal
+
base::Optional<std::vector<uint8_t>> EncodePaddedCBORMap(
cbor::Value::MapValue map) {
+ // TODO: this should pad to 1K, not 256 bytes.
base::Optional<std::vector<uint8_t>> cbor_bytes =
cbor::Writer::Write(cbor::Value(std::move(map)));
if (!cbor_bytes) {
@@ -273,15 +344,12 @@ bool Crypter::IsCounterpartyOfForTesting(const Crypter& other) const {
}
HandshakeInitiator::HandshakeInitiator(
- base::span<const uint8_t, 32> psk_gen_key,
- base::span<const uint8_t, kNonceSize> nonce,
+ base::span<const uint8_t, 32> psk,
base::Optional<base::span<const uint8_t, kP256X962Length>> peer_identity,
bssl::UniquePtr<EC_KEY> local_identity)
- : local_identity_(std::move(local_identity)) {
+ : psk_(fido_parsing_utils::Materialize(psk)),
+ local_identity_(std::move(local_identity)) {
DCHECK(peer_identity.has_value() ^ static_cast<bool>(local_identity_));
- HKDF(psk_.data(), psk_.size(), EVP_sha256(), psk_gen_key.data(),
- psk_gen_key.size(), /*salt=*/nonce.data(), nonce.size(),
- /*info=*/nullptr, 0);
if (peer_identity) {
peer_identity_ =
fido_parsing_utils::Materialize<kP256X962Length>(*peer_identity);
@@ -359,8 +427,8 @@ std::vector<uint8_t> HandshakeInitiator::BuildInitialMessage(
return handshake_message;
}
-base::Optional<std::unique_ptr<Crypter>> HandshakeInitiator::ProcessResponse(
- base::span<const uint8_t> response) {
+base::Optional<std::pair<std::unique_ptr<Crypter>, HandshakeHash>>
+HandshakeInitiator::ProcessResponse(base::span<const uint8_t> response) {
if (response.size() < kP256X962Length) {
FIDO_LOG(DEBUG) << "Handshake response truncated (" << response.size()
<< " bytes)";
@@ -405,15 +473,23 @@ base::Optional<std::unique_ptr<Crypter>> HandshakeInitiator::ProcessResponse(
std::array<uint8_t, 32> read_key, write_key;
std::tie(write_key, read_key) = noise_.traffic_keys();
- return std::make_unique<cablev2::Crypter>(read_key, write_key);
+ return std::make_pair(std::make_unique<cablev2::Crypter>(read_key, write_key),
+ noise_.handshake_hash());
}
-base::Optional<std::pair<std::unique_ptr<Crypter>, std::vector<uint8_t>>>
-RespondToHandshake(
- base::span<const uint8_t, 32> psk_gen_key,
- const NonceAndEID& nonce_and_eid,
- base::Optional<base::span<const uint8_t, kCableIdentityKeySeedSize>>
- identity_seed,
+ResponderResult::ResponderResult(std::unique_ptr<Crypter> in_crypter,
+ std::vector<uint8_t> in_getinfo_bytes,
+ HandshakeHash in_handshake_hash)
+ : crypter(std::move(in_crypter)),
+ getinfo_bytes(std::move(in_getinfo_bytes)),
+ handshake_hash(in_handshake_hash) {}
+ResponderResult::~ResponderResult() = default;
+ResponderResult::ResponderResult(ResponderResult&&) = default;
+
+base::Optional<ResponderResult> RespondToHandshake(
+ base::span<const uint8_t, 32> psk,
+ base::span<const uint8_t, kCableEphemeralIdSize> eid,
+ base::Optional<base::span<const uint8_t, kQRSeedSize>> identity_seed,
base::Optional<base::span<const uint8_t, kP256X962Length>> peer_identity,
base::span<const uint8_t> in,
std::vector<uint8_t>* out_response) {
@@ -435,9 +511,8 @@ RespondToHandshake(
}
Noise noise;
- uint8_t prologue[1 + kCableEphemeralIdSize];
- DCHECK_EQ(nonce_and_eid.second.size(), kCableEphemeralIdSize);
- memcpy(&prologue[1], nonce_and_eid.second.data(), kCableEphemeralIdSize);
+ uint8_t prologue[1 + EXTENT(eid)];
+ memcpy(&prologue[1], eid.data(), eid.size());
if (identity) {
noise.Init(device::Noise::HandshakeType::kNKpsk0);
prologue[0] = 0;
@@ -450,12 +525,6 @@ RespondToHandshake(
noise.MixHash(*peer_identity);
}
- std::array<uint8_t, 32> psk;
- HKDF(psk.data(), psk.size(), EVP_sha256(), psk_gen_key.data(),
- psk_gen_key.size(),
- /*salt=*/nonce_and_eid.first.data(), nonce_and_eid.first.size(),
- /*info=*/nullptr, 0);
-
noise.MixKeyAndHash(psk);
noise.MixHash(peer_point_bytes);
noise.MixKey(peer_point_bytes);
@@ -541,9 +610,39 @@ RespondToHandshake(
std::array<uint8_t, 32> read_key, write_key;
std::tie(read_key, write_key) = noise.traffic_keys();
- return std::make_pair(
+ return base::make_optional<ResponderResult>(
std::make_unique<Crypter>(read_key, write_key),
- std::vector<uint8_t>(getinfo_it->second.GetBytestring()));
+ getinfo_it->second.GetBytestring(), noise.handshake_hash());
+}
+
+bool VerifyPairingSignature(
+ base::span<const uint8_t, kQRSeedSize> identity_seed,
+ base::span<const uint8_t, kP256X962Length> peer_public_key_x962,
+ base::span<const uint8_t, std::tuple_size<HandshakeHash>::value>
+ handshake_hash,
+ base::span<const uint8_t> signature) {
+ 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_seed.data(), identity_seed.size()));
+
+ std::array<uint8_t, SHA256_DIGEST_LENGTH> expected_signature =
+ PairingSignature(identity_key.get(), peer_public_key_x962,
+ handshake_hash);
+ return signature.size() == EXTENT(expected_signature) &&
+ CRYPTO_memcmp(expected_signature.data(), signature.data(),
+ EXTENT(expected_signature)) == 0;
+}
+
+std::vector<uint8_t> CalculatePairingSignature(
+ const EC_KEY* identity_key,
+ base::span<const uint8_t, kP256X962Length> peer_public_key_x962,
+ base::span<const uint8_t, std::tuple_size<HandshakeHash>::value>
+ handshake_hash) {
+ std::array<uint8_t, SHA256_DIGEST_LENGTH> expected_signature =
+ PairingSignature(identity_key, peer_public_key_x962, handshake_hash);
+ return std::vector<uint8_t>(expected_signature.begin(),
+ expected_signature.end());
}
} // namespace cablev2
diff --git a/chromium/device/fido/cable/v2_handshake.h b/chromium/device/fido/cable/v2_handshake.h
index 8027edb402e..3e66a497e40 100644
--- a/chromium/device/fido/cable/v2_handshake.h
+++ b/chromium/device/fido/cable/v2_handshake.h
@@ -16,6 +16,7 @@
#include "components/cbor/values.h"
#include "device/fido/cable/cable_discovery_data.h"
#include "device/fido/cable/noise.h"
+#include "device/fido/cable/v2_constants.h"
#include "device/fido/fido_constants.h"
#include "third_party/boringssl/src/include/openssl/base.h"
@@ -24,8 +25,6 @@ class GURL;
namespace device {
namespace cablev2 {
-constexpr size_t kNonceSize = 10;
-
namespace tunnelserver {
// Base32Ord converts |c| into its base32 value, as defined in
@@ -62,26 +61,41 @@ constexpr uint32_t EncodeDomain(const char label[5], TLD tld) {
tld_value;
}
-// Action enumerates the two possible requests that can be made of a tunnel
-// server: to create a new tunnel or to connect to an existing one.
-enum class Action {
- kNew,
- kConnect,
-};
+// DecodeDomain converts a 22-bit tunnel server domain (as encoded by
+// |EncodeDomain|) into a string in dotted form.
+COMPONENT_EXPORT(DEVICE_FIDO) std::string DecodeDomain(uint32_t domain);
+
+// GetNewTunnelURL converts a 22-bit tunnel server domain (as encoded by
+// |EncodeDomain|), and a tunnel ID, into a WebSockets-based URL for creating a
+// new tunnel.
+COMPONENT_EXPORT(DEVICE_FIDO)
+GURL GetNewTunnelURL(uint32_t domain, base::span<const uint8_t, 16> id);
+
+// GetConnectURL converts a 22-bit tunnel server domain (as encoded by
+// |EncodeDomain|), a routing-ID, and a tunnel ID, into a WebSockets-based URL
+// for connecting to an existing tunnel.
+COMPONENT_EXPORT(DEVICE_FIDO)
+GURL GetConnectURL(uint32_t domain,
+ std::array<uint8_t, kRoutingIdSize> routing_id,
+ base::span<const uint8_t, 16> id);
-// GetURL converts a 22-bit tunnel server domain (as encoded by |EncodeDomain|),
-// an action, and a tunnel ID, into a WebSockets-based URL.
+// GetContactURL gets a URL for contacting a previously-paired authenticator.
+// The |tunnel_server| is assumed to be a valid domain name and should have been
+// taken from a previous call to |DecodeDomain|.
COMPONENT_EXPORT(DEVICE_FIDO)
-GURL GetURL(uint32_t domain, Action action, base::span<const uint8_t, 16> id);
+GURL GetContactURL(const std::string& tunnel_server,
+ base::span<const uint8_t> contact_id);
} // namespace tunnelserver
namespace eid {
+// TODO(agl): this could probably be a class.
+
// Components contains the parts of a decrypted EID.
struct Components {
- uint8_t shard_id;
uint32_t tunnel_server_domain;
+ std::array<uint8_t, kRoutingIdSize> routing_id;
std::array<uint8_t, kNonceSize> nonce;
};
@@ -102,6 +116,39 @@ Components ToComponents(const CableEidArray& eid);
} // namespace eid
+// DerivedValueType enumerates the different types of values that might be
+// derived in caBLEv2 from some secret. The values this this enum are protocol
+// constants and thus must not change over time.
+enum class DerivedValueType : uint32_t {
+ kEIDKey = 1,
+ kTunnelID = 2,
+ kPSK = 3,
+ kPairedSecret = 4,
+ kIdentityKeySeed = 5,
+};
+
+namespace internal {
+COMPONENT_EXPORT(DEVICE_FIDO)
+void Derive(uint8_t* out,
+ size_t out_len,
+ base::span<const uint8_t> secret,
+ base::span<const uint8_t> nonce,
+ DerivedValueType type);
+} // namespace internal
+
+// Derive derives a sub-secret from a secret and nonce. It is not possible to
+// learn anything about |secret| from the value of the sub-secret, assuming that
+// |secret| has sufficient size to prevent full enumeration of the
+// possibilities.
+template <size_t N>
+std::array<uint8_t, N> Derive(base::span<const uint8_t> secret,
+ base::span<const uint8_t> nonce,
+ DerivedValueType type) {
+ std::array<uint8_t, N> ret;
+ internal::Derive(ret.data(), N, secret, nonce, type);
+ return ret;
+}
+
// EncodePaddedCBORMap encodes the given map and pads it to 256 bytes in such a
// way that |DecodePaddedCBORMap| can decode it. The padding is done on the
// assumption that the returned bytes will be encrypted and the encoded size of
@@ -117,12 +164,6 @@ COMPONENT_EXPORT(DEVICE_FIDO)
base::Optional<cbor::Value> DecodePaddedCBORMap(
base::span<const uint8_t> input);
-// NonceAndEID contains both the random nonce chosen for an advert, as well as
-// the EID that was generated from it.
-typedef std::pair<std::array<uint8_t, kNonceSize>,
- std::array<uint8_t, device::kCableEphemeralIdSize>>
- NonceAndEID;
-
// Crypter handles the post-handshake encryption of CTAP2 messages.
class COMPONENT_EXPORT(DEVICE_FIDO) Crypter {
public:
@@ -153,18 +194,20 @@ class COMPONENT_EXPORT(DEVICE_FIDO) Crypter {
uint32_t write_sequence_num_ = 0;
};
+// HandshakeHash is the hashed transcript of a handshake. This can be used as a
+// channel-binding value. See
+// http://www.noiseprotocol.org/noise.html#channel-binding.
+using HandshakeHash = std::array<uint8_t, 32>;
+
// HandshakeInitiator starts a caBLE v2 handshake and processes the single
// response message from the other party. The handshake is always initiated from
// the phone.
class COMPONENT_EXPORT(DEVICE_FIDO) HandshakeInitiator {
public:
HandshakeInitiator(
- // psk_gen_key is either derived from QR-code secrets or comes from
- // pairing data.
- base::span<const uint8_t, 32> psk_gen_key,
- // nonce is randomly generated per advertisement and ensures that BLE
- // adverts are non-deterministic.
- base::span<const uint8_t, kNonceSize> nonce,
+ // psk is derived from the connection nonce and either QR-code secrets
+ // pairing secrets.
+ base::span<const uint8_t, 32> psk,
// peer_identity, if not nullopt, specifies that this is a QR handshake
// and then contains a P-256 public key for the peer. Otherwise this is a
// paired handshake.
@@ -190,9 +233,9 @@ class COMPONENT_EXPORT(DEVICE_FIDO) HandshakeInitiator {
// ProcessResponse processes the handshake response from the peer. If
// successful it returns a |Crypter| for protecting future messages on the
- // connection.
- base::Optional<std::unique_ptr<Crypter>> ProcessResponse(
- base::span<const uint8_t> response);
+ // connection and a handshake transcript for signing over if needed.
+ base::Optional<std::pair<std::unique_ptr<Crypter>, HandshakeHash>>
+ ProcessResponse(base::span<const uint8_t> response);
private:
Noise noise_;
@@ -203,21 +246,36 @@ class COMPONENT_EXPORT(DEVICE_FIDO) HandshakeInitiator {
bssl::UniquePtr<EC_KEY> ephemeral_key_;
};
-// RespondToHandshake responds to a caBLE v2 handshake started by a peer. It
-// returns a Crypter for encrypting and decrypting future messages, as well as
-// the getInfo response from the phone.
+// ResponderResult is the result of a successful handshake from the responder's
+// side. It contains a Crypter for protecting future messages, the contents of
+// the getInfo response given by the peer, and a hash of the handshake
+// transcript.
+struct COMPONENT_EXPORT(DEVICE_FIDO) ResponderResult {
+ ResponderResult(std::unique_ptr<Crypter>,
+ std::vector<uint8_t> getinfo_bytes,
+ HandshakeHash);
+ ~ResponderResult();
+ ResponderResult(const ResponderResult&) = delete;
+ ResponderResult(ResponderResult&&);
+ ResponderResult& operator=(const ResponderResult&) = delete;
+
+ std::unique_ptr<Crypter> crypter;
+ std::vector<uint8_t> getinfo_bytes;
+ const HandshakeHash handshake_hash;
+};
+
+// RespondToHandshake responds to a caBLE v2 handshake started by a peer.
COMPONENT_EXPORT(DEVICE_FIDO)
-base::Optional<std::pair<std::unique_ptr<Crypter>, std::vector<uint8_t>>>
-RespondToHandshake(
- // For the first two arguments see |HandshakeInitiator| comments about
- // |psk_gen_key| and |nonce|, and the |BuildInitialMessage| comment about
- // |eid|.
- base::span<const uint8_t, 32> psk_gen_key,
- const NonceAndEID& nonce_and_eid,
+base::Optional<ResponderResult> RespondToHandshake(
+ // psk is derived from the connection nonce and either QR-code secrets or
+ // pairing secrets.
+ base::span<const uint8_t, 32> psk,
+ // eid is the EID that was advertised for this handshake. This is checked
+ // as part of the handshake.
+ base::span<const uint8_t, kCableEphemeralIdSize> eid,
// identity_seed, if not nullopt, specifies that this is a QR handshake and
// contains the seed for QR key for this client.
- base::Optional<base::span<const uint8_t, kCableIdentityKeySeedSize>>
- identity_seed,
+ base::Optional<base::span<const uint8_t, kQRSeedSize>> identity_seed,
// peer_identity, which must be non-nullopt iff |identity| is nullopt,
// contains the peer's public key as taken from the pairing data.
base::Optional<base::span<const uint8_t, kP256X962Length>> peer_identity,
@@ -226,6 +284,27 @@ RespondToHandshake(
// out_response is set to the response handshake message, if successful.
std::vector<uint8_t>* out_response);
+// VerifyPairingSignature checks that |signature| is a valid signature of
+// |handshake_hash| by |peer_public_key_x962|. This is used by a phone to prove
+// possession of |peer_public_key_x962| since the |handshake_hash| encloses
+// random values generated by the desktop and thus is a fresh value.
+COMPONENT_EXPORT(DEVICE_FIDO)
+bool VerifyPairingSignature(
+ base::span<const uint8_t, kQRSeedSize> identity_seed,
+ base::span<const uint8_t, kP256X962Length> peer_public_key_x962,
+ base::span<const uint8_t, std::tuple_size<HandshakeHash>::value>
+ handshake_hash,
+ base::span<const uint8_t> signature);
+
+// CalculatePairingSignature generates a value that will satisfy
+// |VerifyPairingSignature|.
+COMPONENT_EXPORT(DEVICE_FIDO)
+std::vector<uint8_t> CalculatePairingSignature(
+ const EC_KEY* identity_key,
+ base::span<const uint8_t, kP256X962Length> peer_public_key_x962,
+ base::span<const uint8_t, std::tuple_size<HandshakeHash>::value>
+ handshake_hash);
+
} // namespace cablev2
} // namespace device
diff --git a/chromium/device/fido/cable/v2_handshake_fuzzer.cc b/chromium/device/fido/cable/v2_handshake_fuzzer.cc
index 6dd63595a23..bdc47dd9b66 100644
--- a/chromium/device/fido/cable/v2_handshake_fuzzer.cc
+++ b/chromium/device/fido/cable/v2_handshake_fuzzer.cc
@@ -17,13 +17,11 @@ namespace device {
namespace {
-constexpr std::array<uint8_t, 32> kTestPSKGeneratorKey = {
+constexpr std::array<uint8_t, 32> kTestPSK = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
};
-constexpr std::array<uint8_t, cablev2::kNonceSize> kTestNonce = {
- 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
constexpr std::array<uint8_t, 16> kTestEphemeralID = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
constexpr std::array<uint8_t, 65> kTestPeerIdentity = {
@@ -66,16 +64,13 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* raw_data, size_t size) {
}
if (initiate) {
- cablev2::HandshakeInitiator handshaker(kTestPSKGeneratorKey, kTestNonce,
- peer_identity, std::move(local_key));
+ cablev2::HandshakeInitiator handshaker(kTestPSK, peer_identity,
+ std::move(local_key));
handshaker.BuildInitialMessage(kTestEphemeralID, kTestGetInfoBytes);
handshaker.ProcessResponse(input);
} else {
- cablev2::NonceAndEID nonce_and_eid;
- nonce_and_eid.first = kTestNonce;
- nonce_and_eid.second = kTestEphemeralID;
std::vector<uint8_t> response;
- cablev2::RespondToHandshake(kTestPSKGeneratorKey, nonce_and_eid, local_seed,
+ cablev2::RespondToHandshake(kTestPSK, kTestEphemeralID, local_seed,
peer_identity, input, &response);
}
diff --git a/chromium/device/fido/cable/v2_handshake_unittest.cc b/chromium/device/fido/cable/v2_handshake_unittest.cc
index e6a7b2b7f80..6a146a08302 100644
--- a/chromium/device/fido/cable/v2_handshake_unittest.cc
+++ b/chromium/device/fido/cable/v2_handshake_unittest.cc
@@ -3,8 +3,8 @@
// found in the LICENSE file.
#include "device/fido/cable/v2_handshake.h"
-#include "base/rand_util.h"
#include "components/cbor/values.h"
+#include "crypto/random.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/boringssl/src/include/openssl/ec.h"
#include "third_party/boringssl/src/include/openssl/ec_key.h"
@@ -21,21 +21,21 @@ TEST(CableV2Encoding, TunnelServerURLs) {
constexpr uint32_t encoded =
tunnelserver::EncodeDomain("abcd", tunnelserver::TLD::NET);
uint8_t tunnel_id[16] = {0};
- const GURL url =
- tunnelserver::GetURL(encoded, tunnelserver::Action::kNew, tunnel_id);
+ const GURL url = tunnelserver::GetNewTunnelURL(encoded, tunnel_id);
EXPECT_TRUE(url.spec().find("//abcd.net/") != std::string::npos) << url;
}
TEST(CableV2Encoding, EIDs) {
eid::Components components;
components.tunnel_server_domain = 0x010203;
- components.shard_id = 42;
- base::RandBytes(components.nonce.data(), components.nonce.size());
+ components.routing_id = {9, 10, 11};
+ crypto::RandBytes(components.nonce);
CableEidArray eid = eid::FromComponents(components);
+ EXPECT_TRUE(eid::IsValid(eid));
eid::Components components2 = eid::ToComponents(eid);
- EXPECT_EQ(components.shard_id, components2.shard_id);
+ EXPECT_EQ(components.routing_id, components2.routing_id);
EXPECT_EQ(components.tunnel_server_domain, components2.tunnel_server_domain);
EXPECT_EQ(components.nonce, components2.nonce);
@@ -68,13 +68,56 @@ TEST(CableV2Encoding, PaddedCBOR) {
EXPECT_EQ(1u, decoded->GetMap().size());
}
+std::array<uint8_t, kP256X962Length> PublicKeyOf(const EC_KEY* private_key) {
+ std::array<uint8_t, kP256X962Length> ret;
+ CHECK_EQ(ret.size(),
+ EC_POINT_point2oct(EC_KEY_get0_group(private_key),
+ EC_KEY_get0_public_key(private_key),
+ POINT_CONVERSION_UNCOMPRESSED, ret.data(),
+ ret.size(), /*ctx=*/nullptr));
+ return ret;
+}
+
+TEST(CableV2Encoding, HandshakeSignatures) {
+ static const uint8_t kSeed0[kQRSeedSize] = {0};
+ static const uint8_t kSeed1[kQRSeedSize] = {1};
+
+ bssl::UniquePtr<EC_GROUP> group(
+ EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
+ bssl::UniquePtr<EC_KEY> authenticator_key(
+ EC_KEY_derive_from_secret(group.get(), kSeed0, sizeof(kSeed0)));
+ bssl::UniquePtr<EC_KEY> client_key(
+ EC_KEY_derive_from_secret(group.get(), kSeed1, sizeof(kSeed1)));
+
+ const std::array<uint8_t, kP256X962Length> authenticator_public_key =
+ PublicKeyOf(authenticator_key.get());
+ const std::array<uint8_t, kP256X962Length> client_public_key =
+ PublicKeyOf(client_key.get());
+
+ HandshakeHash handshake_hash = {1};
+
+ std::vector<uint8_t> signature = CalculatePairingSignature(
+ authenticator_key.get(), client_public_key, handshake_hash);
+ EXPECT_TRUE(VerifyPairingSignature(kSeed1, authenticator_public_key,
+ handshake_hash, signature));
+
+ handshake_hash[0] ^= 1;
+ EXPECT_FALSE(VerifyPairingSignature(kSeed1, authenticator_public_key,
+ handshake_hash, signature));
+ handshake_hash[0] ^= 1;
+
+ signature[0] ^= 1;
+ EXPECT_FALSE(VerifyPairingSignature(kSeed1, authenticator_public_key,
+ handshake_hash, signature));
+ signature[0] ^= 1;
+}
+
class CableV2HandshakeTest : public ::testing::Test {
public:
CableV2HandshakeTest() {
- std::fill(psk_gen_key_.begin(), psk_gen_key_.end(), 0);
- std::fill(nonce_and_eid_.first.begin(), nonce_and_eid_.first.end(), 1);
- std::fill(nonce_and_eid_.second.begin(), nonce_and_eid_.second.end(), 2);
- std::fill(identity_seed_.begin(), identity_seed_.end(), 3);
+ std::fill(psk_.begin(), psk_.end(), 0);
+ std::fill(eid_.begin(), eid_.end(), 1);
+ std::fill(identity_seed_.begin(), identity_seed_.end(), 2);
bssl::UniquePtr<EC_GROUP> group(
EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
@@ -88,11 +131,11 @@ class CableV2HandshakeTest : public ::testing::Test {
}
protected:
- std::array<uint8_t, 32> psk_gen_key_;
- NonceAndEID nonce_and_eid_;
+ std::array<uint8_t, 32> psk_;
+ CableEidArray eid_;
bssl::UniquePtr<EC_KEY> identity_key_;
std::array<uint8_t, kP256X962Length> identity_public_;
- std::array<uint8_t, kCableIdentityKeySeedSize> identity_seed_;
+ std::array<uint8_t, kQRSeedSize> identity_seed_;
};
TEST_F(CableV2HandshakeTest, MessageEncrytion) {
@@ -123,34 +166,33 @@ TEST_F(CableV2HandshakeTest, MessageEncrytion) {
}
TEST_F(CableV2HandshakeTest, QRHandshake) {
- std::array<uint8_t, 32> wrong_psk_gen_key = psk_gen_key_;
- wrong_psk_gen_key[0] ^= 1;
+ std::array<uint8_t, 32> wrong_psk = psk_;
+ wrong_psk[0] ^= 1;
uint8_t kGetInfoBytes[] = {1, 2, 3, 4, 5};
for (const bool use_correct_key : {false, true}) {
- HandshakeInitiator initiator(
- use_correct_key ? psk_gen_key_ : wrong_psk_gen_key,
- nonce_and_eid_.first, identity_public_,
- /*local_identity=*/nullptr);
+ HandshakeInitiator initiator(use_correct_key ? psk_ : wrong_psk,
+ identity_public_,
+ /*local_identity=*/nullptr);
std::vector<uint8_t> message =
- initiator.BuildInitialMessage(nonce_and_eid_.second, kGetInfoBytes);
+ initiator.BuildInitialMessage(eid_, kGetInfoBytes);
std::vector<uint8_t> response;
- base::Optional<std::pair<std::unique_ptr<Crypter>, std::vector<uint8_t>>>
- responder_result(RespondToHandshake(
- psk_gen_key_, nonce_and_eid_, identity_seed_,
- /*peer_identity=*/base::nullopt, message, &response));
+ base::Optional<ResponderResult> responder_result(RespondToHandshake(
+ psk_, eid_, identity_seed_,
+ /*peer_identity=*/base::nullopt, message, &response));
ASSERT_EQ(responder_result.has_value(), use_correct_key);
if (!use_correct_key) {
continue;
}
- base::Optional<std::unique_ptr<Crypter>> initiator_result(
- initiator.ProcessResponse(response));
+ base::Optional<std::pair<std::unique_ptr<Crypter>, HandshakeHash>>
+ initiator_result(initiator.ProcessResponse(response));
ASSERT_TRUE(initiator_result.has_value());
- EXPECT_TRUE(responder_result->first->IsCounterpartyOfForTesting(
- *initiator_result.value()));
- ASSERT_EQ(responder_result->second.size(), sizeof(kGetInfoBytes));
- EXPECT_EQ(0, memcmp(responder_result->second.data(), kGetInfoBytes,
+ EXPECT_EQ(initiator_result->second, responder_result->handshake_hash);
+ EXPECT_TRUE(responder_result->crypter->IsCounterpartyOfForTesting(
+ *initiator_result->first));
+ ASSERT_EQ(responder_result->getinfo_bytes.size(), sizeof(kGetInfoBytes));
+ EXPECT_EQ(0, memcmp(responder_result->getinfo_bytes.data(), kGetInfoBytes,
sizeof(kGetInfoBytes)));
}
}
@@ -166,30 +208,28 @@ TEST_F(CableV2HandshakeTest, PairedHandshake) {
EC_KEY* const key = use_correct_key ? identity_key_.get() : wrong_key.get();
EC_KEY_up_ref(key);
- HandshakeInitiator initiator(psk_gen_key_, nonce_and_eid_.first,
+ HandshakeInitiator initiator(psk_,
/*peer_identity=*/base::nullopt,
bssl::UniquePtr<EC_KEY>(key));
std::vector<uint8_t> message =
- initiator.BuildInitialMessage(nonce_and_eid_.second, kGetInfoBytes);
+ initiator.BuildInitialMessage(eid_, kGetInfoBytes);
std::vector<uint8_t> response;
- base::Optional<std::pair<std::unique_ptr<Crypter>, std::vector<uint8_t>>>
- responder_result(RespondToHandshake(psk_gen_key_, nonce_and_eid_,
- /*identity_seed=*/base::nullopt,
- identity_public_, message,
- &response));
+ base::Optional<ResponderResult> responder_result(RespondToHandshake(
+ psk_, eid_,
+ /*identity_seed=*/base::nullopt, identity_public_, message, &response));
ASSERT_EQ(responder_result.has_value(), use_correct_key);
if (!use_correct_key) {
continue;
}
- base::Optional<std::unique_ptr<Crypter>> initiator_result(
- initiator.ProcessResponse(response));
+ base::Optional<std::pair<std::unique_ptr<Crypter>, HandshakeHash>>
+ initiator_result(initiator.ProcessResponse(response));
ASSERT_TRUE(initiator_result.has_value());
- EXPECT_TRUE(responder_result->first->IsCounterpartyOfForTesting(
- *initiator_result.value()));
- ASSERT_EQ(responder_result->second.size(), sizeof(kGetInfoBytes));
- EXPECT_EQ(0, memcmp(responder_result->second.data(), kGetInfoBytes,
+ EXPECT_TRUE(responder_result->crypter->IsCounterpartyOfForTesting(
+ *initiator_result->first));
+ ASSERT_EQ(responder_result->getinfo_bytes.size(), sizeof(kGetInfoBytes));
+ EXPECT_EQ(0, memcmp(responder_result->getinfo_bytes.data(), kGetInfoBytes,
sizeof(kGetInfoBytes)));
}
}
diff --git a/chromium/device/fido/cable/v2_registration.cc b/chromium/device/fido/cable/v2_registration.cc
new file mode 100644
index 00000000000..1c26b620ff1
--- /dev/null
+++ b/chromium/device/fido/cable/v2_registration.cc
@@ -0,0 +1,177 @@
+// 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/v2_registration.h"
+
+#include "base/strings/string_number_conversions.h"
+#include "components/cbor/reader.h"
+#include "components/cbor/values.h"
+#include "components/device_event_log/device_event_log.h"
+#include "components/gcm_driver/gcm_app_handler.h"
+#include "components/gcm_driver/gcm_driver.h"
+#include "components/gcm_driver/instance_id/instance_id.h"
+#include "components/gcm_driver/instance_id/instance_id_driver.h"
+#include "device/fido/fido_parsing_utils.h"
+
+namespace device {
+namespace cablev2 {
+namespace authenticator {
+
+namespace {
+
+static const char kFCMAppId[] = "chrome.android.features.cablev2_authenticator";
+static const char kFCMSenderId[] = "141743603694";
+
+class FCMHandler : public gcm::GCMAppHandler, public Registration {
+ public:
+ FCMHandler(instance_id::InstanceIDDriver* instance_id_driver,
+ base::RepeatingCallback<void(std::unique_ptr<Registration::Event>)>
+ event_callback)
+ : event_callback_(std::move(event_callback)),
+ instance_id_driver_(instance_id_driver),
+ instance_id_(instance_id_driver->GetInstanceID(kFCMAppId)) {
+ gcm::GCMDriver* const gcm_driver = instance_id_->gcm_driver();
+ CHECK(gcm_driver->GetAppHandler(kFCMAppId) == nullptr);
+ instance_id_->gcm_driver()->AddAppHandler(kFCMAppId, this);
+
+ instance_id_->GetToken(
+ kFCMSenderId, instance_id::kGCMScope,
+ /*time_to_live=*/base::TimeDelta(), /*options=*/{},
+ /*flags=*/{},
+ base::BindOnce(&FCMHandler::GetTokenComplete, base::Unretained(this)));
+ }
+
+ ~FCMHandler() override {
+ instance_id_->gcm_driver()->RemoveAppHandler(kFCMAppId);
+ instance_id_driver_->RemoveInstanceID(kFCMAppId);
+ }
+
+ // Registration:
+
+ base::Optional<std::vector<uint8_t>> contact_id() const override {
+ if (!registration_token_) {
+ return base::nullopt;
+ }
+ return std::vector<uint8_t>(registration_token_->begin(),
+ registration_token_->end());
+ }
+
+ // GCMAppHandler:
+
+ void OnMessage(const std::string& app_id,
+ const gcm::IncomingMessage& message) override {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(app_id, kFCMAppId);
+ DCHECK_EQ(message.sender_id, kFCMSenderId);
+
+ if (app_id != kFCMAppId || message.sender_id != kFCMSenderId) {
+ FIDO_LOG(ERROR) << "Discarding FCM message from " << message.sender_id;
+ return;
+ }
+
+ base::Optional<std::unique_ptr<Registration::Event>> event =
+ MessageToEvent(message.data);
+ if (!event) {
+ FIDO_LOG(ERROR) << "Failed to decode FCM message. Ignoring.";
+ return;
+ }
+
+ event_callback_.Run(std::move(*event));
+ }
+
+ void ShutdownHandler() override {}
+ void OnStoreReset() override {}
+ void OnMessagesDeleted(const std::string& app_id) override {}
+ void OnSendError(
+ const std::string& app_id,
+ const gcm::GCMClient::SendErrorDetails& send_error_details) override {}
+ void OnSendAcknowledged(const std::string& app_id,
+ const std::string& message_id) override {}
+ void OnMessageDecryptionFailed(const std::string& app_id,
+ const std::string& message_id,
+ const std::string& error_message) override {}
+ bool CanHandle(const std::string& app_id) const override { return false; }
+
+ private:
+ void GetTokenComplete(const std::string& token,
+ instance_id::InstanceID::Result result) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (result != instance_id::InstanceID::SUCCESS) {
+ FIDO_LOG(ERROR) << "Getting FCM token failed: "
+ << static_cast<int>(result);
+ return;
+ }
+
+ FIDO_LOG(ERROR) << __func__ << " " << token;
+ registration_token_ = token;
+ }
+
+ static base::Optional<std::unique_ptr<Registration::Event>> MessageToEvent(
+ const gcm::MessageData& data) {
+ auto event = std::make_unique<Registration::Event>();
+ gcm::MessageData::const_iterator it = data.find("caBLE.tunnelID");
+ if (it == data.end() ||
+ !base::HexStringToSpan(it->second, event->tunnel_id)) {
+ return base::nullopt;
+ }
+
+ it = data.find("caBLE.routingID");
+ if (it == data.end() ||
+ !base::HexStringToSpan(it->second, event->routing_id)) {
+ return base::nullopt;
+ }
+
+ std::vector<uint8_t> payload_bytes;
+ it = data.find("caBLE.clientPayload");
+ if (it == data.end() ||
+ !base::HexStringToBytes(it->second, &payload_bytes)) {
+ return base::nullopt;
+ }
+
+ base::Optional<cbor::Value> payload = cbor::Reader::Read(payload_bytes);
+ if (!payload || !payload->is_map()) {
+ return base::nullopt;
+ }
+
+ const cbor::Value::MapValue& map = payload->GetMap();
+ cbor::Value::MapValue::const_iterator cbor_it = map.find(cbor::Value(1));
+ if (cbor_it == map.end() || !cbor_it->second.is_bytestring()) {
+ return base::nullopt;
+ }
+ event->pairing_id = cbor_it->second.GetBytestring();
+
+ if (!fido_parsing_utils::CopyCBORBytestring(&event->client_nonce, map, 2)) {
+ return base::nullopt;
+ }
+
+ return event;
+ }
+
+ base::RepeatingCallback<void(std::unique_ptr<Registration::Event>)>
+ event_callback_;
+ instance_id::InstanceIDDriver* const instance_id_driver_;
+ instance_id::InstanceID* const instance_id_;
+ base::Optional<std::string> registration_token_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+};
+
+} // namespace
+
+Registration::~Registration() = default;
+Registration::Event::Event() = default;
+Registration::Event::~Event() = default;
+
+std::unique_ptr<Registration> Register(
+ instance_id::InstanceIDDriver* instance_id_driver,
+ base::RepeatingCallback<void(std::unique_ptr<Registration::Event>)>
+ event_callback) {
+ return std::make_unique<FCMHandler>(instance_id_driver,
+ std::move(event_callback));
+}
+
+} // namespace authenticator
+} // namespace cablev2
+} // namespace device
diff --git a/chromium/device/fido/cable/v2_registration.h b/chromium/device/fido/cable/v2_registration.h
new file mode 100644
index 00000000000..90461edc385
--- /dev/null
+++ b/chromium/device/fido/cable/v2_registration.h
@@ -0,0 +1,64 @@
+// 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_CABLE_V2_REGISTRATION_H_
+#define DEVICE_FIDO_CABLE_V2_REGISTRATION_H_
+
+#include <stdint.h>
+
+#include <array>
+#include <memory>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/optional.h"
+#include "device/fido/cable/v2_constants.h"
+
+namespace instance_id {
+class InstanceIDDriver;
+}
+
+namespace device {
+namespace cablev2 {
+namespace authenticator {
+
+// Registration represents a subscription to events from the tunnel service.
+class Registration {
+ public:
+ // An Event contains the information sent by the tunnel service when a peer is
+ // trying to connect.
+ struct Event {
+ Event();
+ ~Event();
+ Event(const Event&) = delete;
+ Event& operator=(const Event&) = delete;
+
+ std::array<uint8_t, kTunnelIdSize> tunnel_id;
+ std::array<uint8_t, kRoutingIdSize> routing_id;
+ std::vector<uint8_t> pairing_id;
+ std::array<uint8_t, kClientNonceSize> client_nonce;
+ };
+
+ virtual ~Registration();
+
+ // contact_id returns an opaque token that may be placed in pairing data for
+ // desktops to later connect to. |nullopt| will be returned if the value is
+ // not yet ready.
+ virtual base::Optional<std::vector<uint8_t>> contact_id() const = 0;
+};
+
+// Register subscribes to the tunnel service and returns a |Registration|. This
+// should only be called once in an address space. Subsequent calls may CHECK.
+// The |event_callback| is called, on the same thread, whenever a paired device
+// requests a tunnel.
+std::unique_ptr<Registration> Register(
+ instance_id::InstanceIDDriver* instance_id_driver,
+ base::RepeatingCallback<void(std::unique_ptr<Registration::Event>)>
+ event_callback);
+
+} // namespace authenticator
+} // namespace cablev2
+} // namespace device
+
+#endif // DEVICE_FIDO_CABLE_V2_REGISTRATION_H_
diff --git a/chromium/device/fido/cable/v2_test_util.cc b/chromium/device/fido/cable/v2_test_util.cc
new file mode 100644
index 00000000000..1504cd22637
--- /dev/null
+++ b/chromium/device/fido/cable/v2_test_util.cc
@@ -0,0 +1,483 @@
+// 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/v2_test_util.h"
+
+#include <string>
+#include <vector>
+
+#include "base/base64url.h"
+#include "base/bind.h"
+#include "base/check.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string_number_conversions.h"
+#include "components/cbor/reader.h"
+#include "components/cbor/values.h"
+#include "components/cbor/writer.h"
+#include "crypto/random.h"
+#include "device/fido/cable/v2_authenticator.h"
+#include "device/fido/cable/v2_discovery.h"
+#include "device/fido/fido_constants.h"
+#include "device/fido/virtual_ctap2_device.h"
+#include "services/network/test/test_network_context.h"
+#include "url/gurl.h"
+
+namespace device {
+namespace cablev2 {
+namespace {
+
+// TestNetworkContext intercepts WebSocket creation calls and simulates a
+// caBLEv2 tunnel server.
+class TestNetworkContext : public network::TestNetworkContext {
+ public:
+ using ContactCallback = base::RepeatingCallback<void(
+ base::span<const uint8_t, kTunnelIdSize> tunnel_id,
+ base::span<const uint8_t> pairing_id,
+ base::span<const uint8_t, kClientNonceSize> client_nonce)>;
+
+ explicit TestNetworkContext(ContactCallback contact_callback)
+ : contact_callback_(std::move(contact_callback)) {}
+
+ void CreateWebSocket(
+ const GURL& url,
+ const std::vector<std::string>& requested_protocols,
+ const net::SiteForCookies& site_for_cookies,
+ const net::IsolationInfo& isolation_info,
+ std::vector<network::mojom::HttpHeaderPtr> additional_headers,
+ int32_t process_id,
+ int32_t render_frame_id,
+ const url::Origin& origin,
+ uint32_t options,
+ const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
+ mojo::PendingRemote<network::mojom::WebSocketHandshakeClient>
+ handshake_client,
+ mojo::PendingRemote<network::mojom::AuthenticationHandler> auth_handler,
+ mojo::PendingRemote<network::mojom::TrustedHeaderClient> header_client)
+ override {
+ CHECK(url.has_path());
+
+ base::StringPiece path = url.path_piece();
+ static const char kNewPrefix[] = "/cable/new/";
+ static const char kConnectPrefix[] = "/cable/connect/";
+ static const char kContactPrefix[] = "/cable/contact/";
+ if (path.find(kNewPrefix) == 0) {
+ path.remove_prefix(sizeof(kNewPrefix) - 1);
+ CHECK(!base::Contains(connections_, path.as_string()));
+ connections_.emplace(path.as_string(), std::make_unique<Connection>(
+ Connection::Type::NEW,
+ std::move(handshake_client)));
+ } else if (path.find(kConnectPrefix) == 0) {
+ path.remove_prefix(sizeof(kConnectPrefix) - 1);
+ // The first part of |path| will be a hex-encoded routing ID followed by a
+ // '/'. Skip it.
+ constexpr size_t kRoutingIdComponentSize = 2 * kRoutingIdSize + 1;
+ CHECK_GE(path.size(), kRoutingIdComponentSize);
+ path.remove_prefix(kRoutingIdComponentSize);
+
+ const auto it = connections_.find(path.as_string());
+ CHECK(it != connections_.end()) << "Unknown tunnel requested";
+ it->second->set_peer(std::make_unique<Connection>(
+ Connection::Type::CONNECT, std::move(handshake_client)));
+ } else if (path.find(kContactPrefix) == 0) {
+ path.remove_prefix(sizeof(kContactPrefix) - 1);
+
+ CHECK_EQ(additional_headers.size(), 1u);
+ CHECK_EQ(additional_headers[0]->name, device::kCableClientPayloadHeader);
+ std::vector<uint8_t> client_payload_bytes;
+ CHECK(base::HexStringToBytes(additional_headers[0]->value,
+ &client_payload_bytes));
+
+ base::Optional<cbor::Value> client_payload =
+ cbor::Reader::Read(client_payload_bytes);
+ const cbor::Value::MapValue& map = client_payload->GetMap();
+
+ uint8_t tunnel_id[kTunnelIdSize];
+ crypto::RandBytes(tunnel_id);
+
+ connections_.emplace(
+ base::HexEncode(tunnel_id),
+ std::make_unique<Connection>(Connection::Type::CONTACT,
+ std::move(handshake_client)));
+
+ const std::vector<uint8_t>& client_nonce_vec =
+ map.find(cbor::Value(2))->second.GetBytestring();
+ base::span<const uint8_t, kClientNonceSize> client_nonce(
+ client_nonce_vec.data(), client_nonce_vec.size());
+
+ contact_callback_.Run(
+ tunnel_id,
+ /*pairing_id=*/map.find(cbor::Value(1))->second.GetBytestring(),
+ client_nonce);
+ } else {
+ CHECK(false) << "unexpected path: " << path;
+ }
+ }
+
+ private:
+ class Connection : public network::mojom::WebSocket {
+ public:
+ enum class Type {
+ NEW,
+ CONNECT,
+ CONTACT,
+ };
+
+ Connection(Type type,
+ mojo::PendingRemote<network::mojom::WebSocketHandshakeClient>
+ pending_handshake_client)
+ : type_(type),
+ in_watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL),
+ out_watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL),
+ handshake_client_(std::move(pending_handshake_client)) {
+ MojoCreateDataPipeOptions options;
+ memset(&options, 0, sizeof(options));
+ options.struct_size = sizeof(options);
+ options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
+ options.element_num_bytes = sizeof(uint8_t);
+ options.capacity_num_bytes = 1 << 16;
+
+ CHECK_EQ(mojo::CreateDataPipe(&options, &in_producer_, &in_),
+ MOJO_RESULT_OK);
+ CHECK_EQ(mojo::CreateDataPipe(&options, &out_, &out_consumer_),
+ MOJO_RESULT_OK);
+
+ in_watcher_.Watch(in_.get(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ base::BindRepeating(&Connection::OnInPipeReady,
+ base::Unretained(this)));
+ out_watcher_.Watch(out_.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ base::BindRepeating(&Connection::OnOutPipeReady,
+ base::Unretained(this)));
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&Connection::CompleteConnection,
+ base::Unretained(this)));
+ }
+
+ void SendMessage(network::mojom::WebSocketMessageType type,
+ uint64_t length) override {
+ if (!peer_ || !peer_->connected_) {
+ pending_messages_.emplace_back(std::make_tuple(type, length));
+ } else {
+ peer_->client_receiver_->OnDataFrame(/*final=*/true, type, length);
+ }
+
+ if (length > 0) {
+ buffer_.resize(buffer_.size() + length);
+ OnInPipeReady(MOJO_RESULT_OK, mojo::HandleSignalsState());
+ }
+ }
+
+ void StartReceiving() override {}
+ void StartClosingHandshake(uint16_t code,
+ const std::string& reason) override {
+ CHECK(false);
+ }
+
+ void set_peer(std::unique_ptr<Connection> peer) {
+ CHECK(!peer_);
+ peer_ownership_ = std::move(peer);
+ peer_ = peer_ownership_.get();
+ peer_->set_nonowning_peer(this);
+
+ Flush();
+ }
+
+ private:
+ // name is useful when adding debugging messages. The first party to a
+ // tunnel is "A" and the second is "B".
+ const char* name() const {
+ switch (type_) {
+ case Type::NEW:
+ case Type::CONTACT:
+ return "A";
+ case Type::CONNECT:
+ return "B";
+ }
+ }
+
+ void set_nonowning_peer(Connection* peer) {
+ CHECK(!peer_);
+ peer_ = peer;
+ Flush();
+ }
+
+ void CompleteConnection() {
+ CHECK(!connected_);
+ auto response = network::mojom::WebSocketHandshakeResponse::New();
+ response->selected_protocol = device::kCableWebSocketProtocol;
+
+ if (type_ == Type::NEW) {
+ auto header = network::mojom::HttpHeader::New();
+ header->name = device::kCableRoutingIdHeader;
+ std::array<uint8_t, kRoutingIdSize> routing_id = {42};
+ header->value = base::HexEncode(routing_id);
+ response->headers.push_back(std::move(header));
+ }
+
+ handshake_client_->OnConnectionEstablished(
+ socket_.BindNewPipeAndPassRemote(),
+ client_receiver_.BindNewPipeAndPassReceiver(), std::move(response),
+ std::move(out_consumer_), std::move(in_producer_));
+
+ connected_ = true;
+ if (peer_) {
+ peer_->Flush();
+ }
+ }
+
+ void Flush() {
+ if (!peer_->connected_) {
+ return;
+ }
+
+ for (const auto& pending_message : pending_messages_) {
+ peer_->client_receiver_->OnDataFrame(
+ /*final=*/true, pending_message.first, pending_message.second);
+ }
+
+ if (!buffer_.empty()) {
+ peer_->out_watcher_.Arm();
+ }
+ }
+
+ void OnInPipeReady(MojoResult, const mojo::HandleSignalsState&) {
+ const size_t todo = buffer_.size() - buffer_i_;
+ CHECK_GT(todo, 0u);
+
+ // We CHECK that the message fits into Mojo's 32-bit lengths because we
+ // don't expect anything that large in unittests.
+ uint32_t todo_32 = todo;
+ CHECK_LE(todo, std::numeric_limits<decltype(todo_32)>::max());
+
+ const MojoResult result = in_->ReadData(
+ &buffer_.data()[buffer_i_], &todo_32, MOJO_READ_DATA_FLAG_NONE);
+ if (result == MOJO_RESULT_OK) {
+ buffer_i_ += todo_32;
+ CHECK_LE(buffer_i_, buffer_.size());
+
+ if (peer_ && buffer_i_ > 0) {
+ peer_->OnOutPipeReady(MOJO_RESULT_OK, mojo::HandleSignalsState());
+ }
+
+ if (buffer_i_ < buffer_.size()) {
+ in_watcher_.Arm();
+ } else {
+ // TODO
+ }
+ } else if (result == MOJO_RESULT_SHOULD_WAIT) {
+ in_watcher_.Arm();
+ } else {
+ CHECK(false) << static_cast<int>(result);
+ }
+ }
+
+ void OnOutPipeReady(MojoResult, const mojo::HandleSignalsState&) {
+ const size_t todo = peer_->buffer_.size();
+ if (todo == 0) {
+ return;
+ }
+
+ uint32_t todo_32 = todo;
+ const MojoResult result = out_->WriteData(peer_->buffer_.data(), &todo_32,
+ MOJO_WRITE_DATA_FLAG_NONE);
+ if (result == MOJO_RESULT_OK) {
+ if (todo_32 == todo) {
+ peer_->buffer_.clear();
+ peer_->buffer_i_ = 0;
+ } else {
+ const size_t new_length = todo - todo_32;
+ memmove(peer_->buffer_.data(), &peer_->buffer_.data()[todo_32],
+ new_length);
+ peer_->buffer_.resize(new_length);
+ peer_->buffer_i_ -= todo_32;
+ }
+
+ if (!peer_->buffer_.empty()) {
+ out_watcher_.Arm();
+ }
+ } else if (result == MOJO_RESULT_SHOULD_WAIT) {
+ out_watcher_.Arm();
+ } else {
+ CHECK(false) << static_cast<int>(result);
+ }
+ }
+
+ const Type type_;
+ bool connected_ = false;
+ std::unique_ptr<Connection> peer_ownership_;
+ std::vector<uint8_t> buffer_;
+ std::vector<std::pair<network::mojom::WebSocketMessageType, uint64_t>>
+ pending_messages_;
+ size_t buffer_i_ = 0;
+ mojo::SimpleWatcher in_watcher_;
+ mojo::SimpleWatcher out_watcher_;
+ Connection* peer_ = nullptr;
+ mojo::Remote<network::mojom::WebSocketHandshakeClient> handshake_client_;
+ mojo::Remote<network::mojom::WebSocketClient> client_receiver_;
+ mojo::Receiver<network::mojom::WebSocket> socket_{this};
+ mojo::ScopedDataPipeConsumerHandle in_;
+ mojo::ScopedDataPipeProducerHandle in_producer_;
+ mojo::ScopedDataPipeProducerHandle out_;
+ mojo::ScopedDataPipeConsumerHandle out_consumer_;
+ };
+
+ std::map<std::string, std::unique_ptr<Connection>> connections_;
+ const ContactCallback contact_callback_;
+};
+
+class DummyBLEAdvert
+ : public device::cablev2::authenticator::Platform::BLEAdvert {};
+
+// TestPlatform implements the platform support for caBLEv2 by forwarding
+// messages to the given |VirtualCtap2Device|.
+class TestPlatform : public authenticator::Platform {
+ public:
+ TestPlatform(Discovery* discovery, device::VirtualCtap2Device* ctap2_device)
+ : discovery_(discovery), ctap2_device_(ctap2_device) {}
+
+ void MakeCredential(const std::string& origin,
+ const std::string& rp_id,
+ base::span<const uint8_t> challenge,
+ base::span<const uint8_t> user_id,
+ base::span<const int> algorithms,
+ base::span<const std::vector<uint8_t>> excluded_cred_ids,
+ bool resident_key_required,
+ MakeCredentialCallback callback) override {
+ std::string challenge_b64;
+ base::Base64UrlEncode(
+ base::StringPiece(reinterpret_cast<const char*>(challenge.data()),
+ challenge.size()),
+ base::Base64UrlEncodePolicy::OMIT_PADDING, &challenge_b64);
+
+ std::string client_data_json = base::StringPrintf(
+ R"({"type": "webauthn.create", "challenge": "%s", "origin": "%s",
+ "androidPackageName": "com.chrome.unittest"})",
+ challenge_b64.c_str(), origin.c_str());
+ std::vector<device::PublicKeyCredentialParams::CredentialInfo> cred_infos;
+ for (const auto& algo : algorithms) {
+ device::PublicKeyCredentialParams::CredentialInfo cred_info;
+ cred_info.algorithm = algo;
+ cred_infos.push_back(cred_info);
+ }
+
+ device::CtapMakeCredentialRequest request(
+ client_data_json, device::PublicKeyCredentialRpEntity(rp_id),
+ device::PublicKeyCredentialUserEntity(
+ device::fido_parsing_utils::Materialize(user_id),
+ /*name=*/base::nullopt, /*display_name=*/base::nullopt,
+ /*icon_url=*/base::nullopt),
+ device::PublicKeyCredentialParams(std::move(cred_infos)));
+
+ std::pair<device::CtapRequestCommand, base::Optional<cbor::Value>>
+ request_cbor = AsCTAPRequestValuePair(request);
+
+ ctap2_device_->DeviceTransact(
+ ToCTAP2Command(std::move(request_cbor)),
+ base::BindOnce(&TestPlatform::OnMakeCredentialResult,
+ weak_factory_.GetWeakPtr(), std::move(client_data_json),
+ std::move(callback)));
+ }
+
+ void GetAssertion(const std::string& origin,
+ const std::string& rp_id,
+ base::span<const uint8_t> challenge,
+ base::span<const std::vector<uint8_t>> allowed_cred_ids,
+ GetAssertionCallback callback) override {
+ NOTREACHED();
+ }
+
+ std::unique_ptr<authenticator::Platform::BLEAdvert> SendBLEAdvert(
+ base::span<uint8_t, 16> payload) override {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ [](Discovery* discovery, std::array<uint8_t, 16> payload) {
+ discovery->OnBLEAdvertSeen(
+ /*address=*/"", payload);
+ },
+ base::Unretained(discovery_),
+ device::fido_parsing_utils::Materialize<EXTENT(payload)>(payload)));
+ return std::make_unique<DummyBLEAdvert>();
+ }
+
+ private:
+ std::vector<uint8_t> ToCTAP2Command(
+ const std::pair<device::CtapRequestCommand, base::Optional<cbor::Value>>&
+ parts) {
+ std::vector<uint8_t> ret;
+
+ if (parts.second.has_value()) {
+ base::Optional<std::vector<uint8_t>> cbor_bytes =
+ cbor::Writer::Write(std::move(*parts.second));
+ ret.swap(*cbor_bytes);
+ }
+
+ ret.insert(ret.begin(), static_cast<uint8_t>(parts.first));
+ return ret;
+ }
+
+ void OnMakeCredentialResult(std::string client_data_json,
+ MakeCredentialCallback callback,
+ base::Optional<std::vector<uint8_t>> result) {
+ if (!result || result->empty()) {
+ std::move(callback).Run(
+ static_cast<uint32_t>(device::CtapDeviceResponseCode::kCtap2ErrOther),
+ base::span<const uint8_t>(), base::span<const uint8_t>());
+ return;
+ }
+ const base::span<const uint8_t> payload = *result;
+
+ if (payload.size() == 1 ||
+ payload[0] !=
+ static_cast<uint8_t>(device::CtapDeviceResponseCode::kSuccess)) {
+ std::move(callback).Run(payload[0], base::span<const uint8_t>(),
+ base::span<const uint8_t>());
+ return;
+ }
+
+ base::Optional<cbor::Value> v = cbor::Reader::Read(payload.subspan(1));
+ const cbor::Value::MapValue& in_map = v->GetMap();
+
+ cbor::Value::MapValue out_map;
+ out_map.emplace("fmt", in_map.find(cbor::Value(1))->second.GetString());
+ out_map.emplace("authData",
+ in_map.find(cbor::Value(2))->second.GetBytestring());
+ out_map.emplace("attStmt", in_map.find(cbor::Value(3))->second.GetMap());
+
+ base::Optional<std::vector<uint8_t>> attestation_obj =
+ cbor::Writer::Write(cbor::Value(std::move(out_map)));
+
+ std::move(callback).Run(
+ static_cast<uint32_t>(device::CtapDeviceResponseCode::kSuccess),
+ base::span<const uint8_t>(
+ reinterpret_cast<const uint8_t*>(client_data_json.data()),
+ client_data_json.size()),
+ *attestation_obj);
+ }
+
+ Discovery* const discovery_;
+ device::VirtualCtap2Device* const ctap2_device_;
+ base::WeakPtrFactory<TestPlatform> weak_factory_{this};
+};
+
+} // namespace
+
+std::unique_ptr<network::mojom::NetworkContext> NewMockTunnelServer(
+ ContactCallback contact_callback) {
+ return std::make_unique<TestNetworkContext>(std::move(contact_callback));
+}
+
+namespace authenticator {
+
+std::unique_ptr<authenticator::Platform> NewMockPlatform(
+ Discovery* discovery,
+ device::VirtualCtap2Device* ctap2_device) {
+ return std::make_unique<TestPlatform>(discovery, ctap2_device);
+}
+
+} // namespace authenticator
+
+} // namespace cablev2
+} // namespace device
diff --git a/chromium/device/fido/cable/v2_test_util.h b/chromium/device/fido/cable/v2_test_util.h
new file mode 100644
index 00000000000..701a14a71b0
--- /dev/null
+++ b/chromium/device/fido/cable/v2_test_util.h
@@ -0,0 +1,52 @@
+// 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_CABLE_V2_TEST_UTIL_H_
+#define DEVICE_FIDO_CABLE_V2_TEST_UTIL_H_
+
+#include <memory>
+
+#include "base/callback_forward.h"
+#include "base/containers/span.h"
+#include "device/fido/cable/v2_constants.h"
+#include "services/network/public/mojom/network_context.mojom-forward.h"
+
+namespace device {
+
+class VirtualCtap2Device;
+
+namespace cablev2 {
+
+class Discovery;
+
+// ContactCallback is called when a mock tunnel server (see
+// |NewMockTunnelServer|) is asked to contact a phone. This simulates a tunnel
+// server using a cloud messaging solution to wake a device.
+using ContactCallback = base::RepeatingCallback<void(
+ base::span<const uint8_t, kTunnelIdSize> tunnel_id,
+ base::span<const uint8_t> pairing_id,
+ base::span<const uint8_t, kClientNonceSize> client_nonce)>;
+
+// NewMockTunnelServer returns a |NetworkContext| that implements WebSocket
+// requests and simulates a tunnel server.
+std::unique_ptr<network::mojom::NetworkContext> NewMockTunnelServer(
+ ContactCallback contact_callback);
+
+namespace authenticator {
+
+class Platform;
+
+// NewMockPlatform returns a |Platform| that implements the makeCredential
+// operation by forwarding it to |ctap2_device|. Transmitted BLE adverts are
+// forwarded to |discovery|.
+std::unique_ptr<Platform> NewMockPlatform(
+ Discovery* discovery,
+ device::VirtualCtap2Device* ctap2_device);
+
+} // namespace authenticator
+
+} // namespace cablev2
+} // namespace device
+
+#endif // DEVICE_FIDO_CABLE_V2_TEST_UTIL_H_
diff --git a/chromium/device/fido/cable/websocket_adapter.cc b/chromium/device/fido/cable/websocket_adapter.cc
index 4c10120cc0e..b522f58d1cf 100644
--- a/chromium/device/fido/cable/websocket_adapter.cc
+++ b/chromium/device/fido/cable/websocket_adapter.cc
@@ -20,7 +20,9 @@ static constexpr size_t kMaxIncomingMessageSize = 1 << 20;
WebSocketAdapter::WebSocketAdapter(TunnelReadyCallback on_tunnel_ready,
TunnelDataCallback on_tunnel_data)
: on_tunnel_ready_(std::move(on_tunnel_ready)),
- on_tunnel_data_(std::move(on_tunnel_data)) {}
+ on_tunnel_data_(std::move(on_tunnel_data)),
+ read_pipe_watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL) {
+}
WebSocketAdapter::~WebSocketAdapter() = default;
@@ -68,61 +70,78 @@ void WebSocketAdapter::OnConnectionEstablished(
return;
}
- base::Optional<uint8_t> shard_id;
+ base::Optional<std::array<uint8_t, kRoutingIdSize>> routing_id;
for (const auto& header : response->headers) {
if (base::EqualsCaseInsensitiveASCII(header->name.c_str(),
- kCableShardIdHeader)) {
- unsigned u;
- if (!base::StringToUint(header->value, &u) || shard_id > 63) {
- FIDO_LOG(ERROR) << "Invalid shard ID from tunnel server";
+ kCableRoutingIdHeader)) {
+ if (routing_id.has_value() ||
+ !base::HexStringToSpan(header->value, routing_id.emplace())) {
+ FIDO_LOG(ERROR) << "Invalid routing ID from tunnel server: "
+ << header->value;
return;
}
- shard_id = u;
- break;
}
}
socket_remote_.Bind(std::move(socket));
read_pipe_ = std::move(readable);
+ read_pipe_watcher_.Watch(
+ read_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE,
+ MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
+ base::BindRepeating(&WebSocketAdapter::OnDataPipeReady,
+ base::Unretained(this)));
write_pipe_ = std::move(writable);
client_receiver_.Bind(std::move(client_receiver));
+
+ // |handshake_receiver_| will disconnect soon. In order to catch network
+ // process crashes, we switch to watching |client_receiver_|.
+ handshake_receiver_.set_disconnect_handler(base::DoNothing());
+ client_receiver_.set_disconnect_handler(base::BindOnce(
+ &WebSocketAdapter::OnMojoPipeDisconnect, base::Unretained(this)));
+
socket_remote_->StartReceiving();
- std::move(on_tunnel_ready_).Run(true, shard_id);
+ std::move(on_tunnel_ready_).Run(true, routing_id);
}
void WebSocketAdapter::OnDataFrame(bool finish,
network::mojom::WebSocketMessageType type,
uint64_t data_len) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_EQ(pending_message_i_, pending_message_.size());
+ DCHECK(!pending_message_finished_);
+
+ if (data_len == 0) {
+ if (finish) {
+ FlushPendingMessage();
+ }
+ return;
+ }
const size_t old_size = pending_message_.size();
const size_t new_size = old_size + data_len;
if (type != network::mojom::WebSocketMessageType::BINARY ||
data_len > std::numeric_limits<uint32_t>::max() || new_size < old_size ||
new_size > kMaxIncomingMessageSize) {
- FIDO_LOG(ERROR) << "invalid WebSocket frame";
+ FIDO_LOG(ERROR) << "invalid WebSocket frame (type: "
+ << static_cast<int>(type) << ", len: " << data_len << ")";
Close();
return;
}
- if (data_len > 0) {
- pending_message_.resize(new_size);
- uint32_t data_len_32 = data_len;
- if (read_pipe_->ReadData(&pending_message_.data()[old_size], &data_len_32,
- MOJO_READ_DATA_FLAG_ALL_OR_NONE) !=
- MOJO_RESULT_OK) {
- FIDO_LOG(ERROR) << "reading WebSocket frame failed";
- Close();
- return;
- }
- DCHECK_EQ(static_cast<size_t>(data_len_32), data_len);
- }
-
- if (finish) {
- on_tunnel_data_.Run(pending_message_);
- pending_message_.resize(0);
- }
+ // The network process sends the |OnDataFrame| message before writing to
+ // |read_pipe_|. Therefore we cannot depend on the message bytes being
+ // immediately available in |read_pipe_| without a race. Thus
+ // |read_pipe_watcher_| is used to wait for the data if needed.
+
+ pending_message_.resize(new_size);
+ pending_message_finished_ = finish;
+ // Suspend more |OnDataFrame| callbacks until frame's data has been read. The
+ // network service has successfully read |data_len| bytes before calling this
+ // function so there's no I/O errors to worry about while reading; we know
+ // that the bytes are coming.
+ client_receiver_.Pause();
+ OnDataPipeReady(MOJO_RESULT_OK, mojo::HandleSignalsState());
}
void WebSocketAdapter::OnDropChannel(bool was_clean,
@@ -137,6 +156,41 @@ void WebSocketAdapter::OnClosingHandshake() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
+void WebSocketAdapter::OnDataPipeReady(MojoResult,
+ const mojo::HandleSignalsState&) {
+ const size_t todo = pending_message_.size() - pending_message_i_;
+ DCHECK_GT(todo, 0u);
+
+ // Truncation to 32-bits cannot overflow because |pending_message_.size()| is
+ // bound by |kMaxIncomingMessageSize| when it is resized in |OnDataFrame|.
+ uint32_t todo_32 = static_cast<uint32_t>(todo);
+ static_assert(
+ kMaxIncomingMessageSize <= std::numeric_limits<decltype(todo_32)>::max(),
+ "");
+ const MojoResult result =
+ read_pipe_->ReadData(&pending_message_.data()[pending_message_i_],
+ &todo_32, MOJO_READ_DATA_FLAG_NONE);
+ if (result == MOJO_RESULT_OK) {
+ pending_message_i_ += todo_32;
+ DCHECK_LE(pending_message_i_, pending_message_.size());
+
+ if (pending_message_i_ < pending_message_.size()) {
+ read_pipe_watcher_.Arm();
+ } else {
+ client_receiver_.Resume();
+ if (pending_message_finished_) {
+ FlushPendingMessage();
+ }
+ }
+ } else if (result == MOJO_RESULT_SHOULD_WAIT) {
+ read_pipe_watcher_.Arm();
+ } else {
+ FIDO_LOG(ERROR) << "reading WebSocket frame failed: "
+ << static_cast<int>(result);
+ Close();
+ }
+}
+
void WebSocketAdapter::OnMojoPipeDisconnect() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -160,5 +214,14 @@ void WebSocketAdapter::Close() {
on_tunnel_data_.Run(base::nullopt);
}
+void WebSocketAdapter::FlushPendingMessage() {
+ std::vector<uint8_t> message;
+ message.swap(pending_message_);
+ pending_message_i_ = 0;
+ pending_message_finished_ = false;
+
+ on_tunnel_data_.Run(message);
+}
+
} // namespace cablev2
} // namespace device
diff --git a/chromium/device/fido/cable/websocket_adapter.h b/chromium/device/fido/cable/websocket_adapter.h
index c898fc7cd09..40289bd0bd7 100644
--- a/chromium/device/fido/cable/websocket_adapter.h
+++ b/chromium/device/fido/cable/websocket_adapter.h
@@ -12,6 +12,7 @@
#include "base/containers/span.h"
#include "base/optional.h"
#include "base/sequence_checker.h"
+#include "device/fido/cable/v2_handshake.h"
#include "services/network/public/mojom/network_context.mojom.h"
namespace device {
@@ -24,14 +25,13 @@ class COMPONENT_EXPORT(DEVICE_FIDO) WebSocketAdapter
: public network::mojom::WebSocketHandshakeClient,
network::mojom::WebSocketClient {
public:
- using TunnelReadyCallback =
- base::OnceCallback<void(bool, base::Optional<uint8_t>)>;
+ using TunnelReadyCallback = base::OnceCallback<
+ void(bool, base::Optional<std::array<uint8_t, kRoutingIdSize>>)>;
using TunnelDataCallback =
base::RepeatingCallback<void(base::Optional<base::span<const uint8_t>>)>;
WebSocketAdapter(
// on_tunnel_ready is called once with a boolean that indicates whether
- // the WebSocket successfully connected and an optional shard ID taken
- // from the X-caBLE-Shard header in the HTTP response, if any.
+ // the WebSocket successfully connected and an optional routing ID.
TunnelReadyCallback on_tunnel_ready,
// on_tunnel_ready is called repeatedly, after successful connection, with
// the contents of WebSocket messages. Framing is preserved so a single
@@ -72,11 +72,22 @@ class COMPONENT_EXPORT(DEVICE_FIDO) WebSocketAdapter
private:
void OnMojoPipeDisconnect();
+ void OnDataPipeReady(MojoResult result,
+ const mojo::HandleSignalsState& state);
void Close();
+ void FlushPendingMessage();
bool closed_ = false;
+
// pending_message_ contains a partial message that is being reassembled.
std::vector<uint8_t> pending_message_;
+ // pending_message_i_ contains the number of valid bytes of
+ // |pending_message_|.
+ size_t pending_message_i_ = 0;
+ // pending_message_finished_ is true if |pending_message_| is the full size of
+ // an application frame and thus should be passed up once filled with bytes.
+ bool pending_message_finished_ = false;
+
TunnelReadyCallback on_tunnel_ready_;
const TunnelDataCallback on_tunnel_data_;
mojo::Receiver<network::mojom::WebSocketHandshakeClient> handshake_receiver_{
@@ -84,6 +95,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) WebSocketAdapter
mojo::Receiver<network::mojom::WebSocketClient> client_receiver_{this};
mojo::Remote<network::mojom::WebSocket> socket_remote_;
mojo::ScopedDataPipeConsumerHandle read_pipe_;
+ mojo::SimpleWatcher read_pipe_watcher_;
mojo::ScopedDataPipeProducerHandle write_pipe_;
SEQUENCE_CHECKER(sequence_checker_);
};
diff --git a/chromium/device/fido/client_data.cc b/chromium/device/fido/client_data.cc
index 5ee8a6dc6cd..eb04d50fb61 100644
--- a/chromium/device/fido/client_data.cc
+++ b/chromium/device/fido/client_data.cc
@@ -102,7 +102,7 @@ std::string SerializeCollectedClientDataToJson(
// unreasonably specific assumptions about the clientData JSON. This is
// done in the fashion of
// https://tools.ietf.org/html/draft-ietf-tls-grease
- ret.append(R"(,"extra_keys_may_be_added_here":")");
+ ret.append(R"(,"other_keys_can_be_added_here":")");
ret.append(
"do not compare clientDataJSON against a template. See "
"https://goo.gl/yabPex\"");
diff --git a/chromium/device/fido/credential_management.cc b/chromium/device/fido/credential_management.cc
index 3e9a9b6aa76..9ec1fdfc85c 100644
--- a/chromium/device/fido/credential_management.cc
+++ b/chromium/device/fido/credential_management.cc
@@ -15,31 +15,11 @@
namespace device {
-namespace {
-std::array<uint8_t, 16> MakePINAuth(base::span<const uint8_t> pin_token,
- base::span<const uint8_t> pin_auth_bytes) {
- DCHECK(!pin_token.empty() && !pin_auth_bytes.empty());
- std::array<uint8_t, SHA256_DIGEST_LENGTH> hmac;
- unsigned hmac_len;
- CHECK(HMAC(EVP_sha256(), pin_token.data(), pin_token.size(),
- pin_auth_bytes.data(), pin_auth_bytes.size(), hmac.data(),
- &hmac_len));
- DCHECK_EQ(hmac.size(), static_cast<size_t>(hmac_len));
- std::array<uint8_t, 16> pin_auth;
- std::copy(hmac.begin(), hmac.begin() + 16, pin_auth.begin());
- return pin_auth;
-}
-} // namespace
-
CredentialManagementRequest::CredentialManagementRequest(
Version version_,
CredentialManagementSubCommand subcommand_,
- base::Optional<cbor::Value::MapValue> params_,
- base::Optional<std::array<uint8_t, 16>> pin_auth_)
- : version(version_),
- subcommand(subcommand_),
- params(std::move(params_)),
- pin_auth(std::move(pin_auth_)) {}
+ base::Optional<cbor::Value::MapValue> params_)
+ : version(version_), subcommand(subcommand_), params(std::move(params_)) {}
CredentialManagementRequest::CredentialManagementRequest(
CredentialManagementRequest&&) = default;
CredentialManagementRequest& CredentialManagementRequest::operator=(
@@ -49,25 +29,25 @@ CredentialManagementRequest::~CredentialManagementRequest() = default;
// static
CredentialManagementRequest CredentialManagementRequest::ForGetCredsMetadata(
Version version,
- base::span<const uint8_t> pin_token) {
- return CredentialManagementRequest(
+ const pin::TokenResponse& token) {
+ CredentialManagementRequest request(
version, CredentialManagementSubCommand::kGetCredsMetadata,
- /*params=*/base::nullopt,
- MakePINAuth(pin_token,
- {{static_cast<uint8_t>(
- CredentialManagementSubCommand::kGetCredsMetadata)}}));
+ /*params=*/base::nullopt);
+ request.pin_auth = token.PinAuth({{static_cast<uint8_t>(
+ CredentialManagementSubCommand::kGetCredsMetadata)}});
+ return request;
}
// static
CredentialManagementRequest CredentialManagementRequest::ForEnumerateRPsBegin(
Version version,
- base::span<const uint8_t> pin_token) {
- return CredentialManagementRequest(
+ const pin::TokenResponse& token) {
+ CredentialManagementRequest request(
version, CredentialManagementSubCommand::kEnumerateRPsBegin,
- /*params=*/base::nullopt,
- MakePINAuth(pin_token,
- {{static_cast<uint8_t>(
- CredentialManagementSubCommand::kEnumerateRPsBegin)}}));
+ /*params=*/base::nullopt);
+ request.pin_auth = token.PinAuth({{static_cast<uint8_t>(
+ CredentialManagementSubCommand::kEnumerateRPsBegin)}});
+ return request;
}
// static
@@ -75,30 +55,30 @@ CredentialManagementRequest CredentialManagementRequest::ForEnumerateRPsGetNext(
Version version) {
return CredentialManagementRequest(
version, CredentialManagementSubCommand::kEnumerateRPsGetNextRP,
- /*params=*/base::nullopt,
- /*pin_auth=*/base::nullopt);
+ /*params=*/base::nullopt);
}
// static
CredentialManagementRequest
CredentialManagementRequest::ForEnumerateCredentialsBegin(
Version version,
- base::span<const uint8_t> pin_token,
+ const pin::TokenResponse& token,
std::array<uint8_t, kRpIdHashLength> rp_id_hash) {
cbor::Value::MapValue params_map;
params_map.emplace(
static_cast<int>(CredentialManagementRequestParamKey::kRPIDHash),
std::move(rp_id_hash));
- base::Optional<std::vector<uint8_t>> pin_auth_bytes =
- cbor::Writer::Write(cbor::Value(params_map));
- DCHECK(pin_auth_bytes);
- pin_auth_bytes->insert(
- pin_auth_bytes->begin(),
+ std::vector<uint8_t> pin_auth_bytes =
+ *cbor::Writer::Write(cbor::Value(params_map));
+ CredentialManagementRequest request(
+ version, CredentialManagementSubCommand::kEnumerateCredentialsBegin,
+ std::move(params_map));
+ pin_auth_bytes.insert(
+ pin_auth_bytes.begin(),
static_cast<uint8_t>(
CredentialManagementSubCommand::kEnumerateCredentialsBegin));
- return CredentialManagementRequest(
- version, CredentialManagementSubCommand::kEnumerateCredentialsBegin,
- std::move(params_map), MakePINAuth(pin_token, *pin_auth_bytes));
+ request.pin_auth = token.PinAuth(pin_auth_bytes);
+ return request;
}
// static
@@ -107,27 +87,28 @@ CredentialManagementRequest::ForEnumerateCredentialsGetNext(Version version) {
return CredentialManagementRequest(
version,
CredentialManagementSubCommand::kEnumerateCredentialsGetNextCredential,
- /*params=*/base::nullopt, /*pin_auth=*/base::nullopt);
+ /*params=*/base::nullopt);
}
// static
CredentialManagementRequest CredentialManagementRequest::ForDeleteCredential(
Version version,
- base::span<const uint8_t> pin_token,
+ const pin::TokenResponse& token,
const PublicKeyCredentialDescriptor& credential_id) {
cbor::Value::MapValue params_map;
params_map.emplace(
static_cast<int>(CredentialManagementRequestParamKey::kCredentialID),
AsCBOR(credential_id));
- base::Optional<std::vector<uint8_t>> pin_auth_bytes =
- cbor::Writer::Write(cbor::Value(params_map));
- DCHECK(pin_auth_bytes);
- pin_auth_bytes->insert(
- pin_auth_bytes->begin(),
- static_cast<uint8_t>(CredentialManagementSubCommand::kDeleteCredential));
- return CredentialManagementRequest(
+ std::vector<uint8_t> pin_auth_bytes =
+ *cbor::Writer::Write(cbor::Value(params_map));
+ CredentialManagementRequest request(
version, CredentialManagementSubCommand::kDeleteCredential,
- std::move(params_map), MakePINAuth(pin_token, *pin_auth_bytes));
+ std::move(params_map));
+ pin_auth_bytes.insert(
+ pin_auth_bytes.begin(),
+ static_cast<uint8_t>(CredentialManagementSubCommand::kDeleteCredential));
+ request.pin_auth = token.PinAuth(pin_auth_bytes);
+ return request;
}
// static
@@ -349,8 +330,9 @@ AggregatedEnumerateCredentialsResponse::AggregatedEnumerateCredentialsResponse(
: rp(std::move(rp_)), credentials() {}
AggregatedEnumerateCredentialsResponse::AggregatedEnumerateCredentialsResponse(
AggregatedEnumerateCredentialsResponse&&) = default;
-AggregatedEnumerateCredentialsResponse& AggregatedEnumerateCredentialsResponse::
-operator=(AggregatedEnumerateCredentialsResponse&&) = default;
+AggregatedEnumerateCredentialsResponse&
+AggregatedEnumerateCredentialsResponse::operator=(
+ AggregatedEnumerateCredentialsResponse&&) = default;
AggregatedEnumerateCredentialsResponse::
~AggregatedEnumerateCredentialsResponse() = default;
diff --git a/chromium/device/fido/credential_management.h b/chromium/device/fido/credential_management.h
index 7f24fae7735..b0d3725e200 100644
--- a/chromium/device/fido/credential_management.h
+++ b/chromium/device/fido/credential_management.h
@@ -8,6 +8,7 @@
#include "base/component_export.h"
#include "base/optional.h"
#include "device/fido/fido_constants.h"
+#include "device/fido/pin.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"
@@ -96,40 +97,36 @@ struct CredentialManagementRequest {
static CredentialManagementRequest ForGetCredsMetadata(
Version version,
- base::span<const uint8_t> pin_token);
+ const pin::TokenResponse& token);
static CredentialManagementRequest ForEnumerateRPsBegin(
Version version,
- base::span<const uint8_t> pin_token);
+ const pin::TokenResponse& token);
static CredentialManagementRequest ForEnumerateRPsGetNext(Version version);
static CredentialManagementRequest ForEnumerateCredentialsBegin(
Version version,
- base::span<const uint8_t> pin_token,
+ const pin::TokenResponse& token,
std::array<uint8_t, kRpIdHashLength> rp_id_hash);
static CredentialManagementRequest ForEnumerateCredentialsGetNext(
Version version);
static CredentialManagementRequest ForDeleteCredential(
Version version,
- base::span<const uint8_t> pin_token,
+ const pin::TokenResponse& token,
const PublicKeyCredentialDescriptor& credential_id);
+ CredentialManagementRequest(Version version,
+ CredentialManagementSubCommand subcommand,
+ base::Optional<cbor::Value::MapValue> params);
CredentialManagementRequest(CredentialManagementRequest&&);
CredentialManagementRequest& operator=(CredentialManagementRequest&&);
+ CredentialManagementRequest(const CredentialManagementRequest&) = delete;
+ CredentialManagementRequest& operator=(const CredentialManagementRequest&) =
+ delete;
~CredentialManagementRequest();
Version version;
CredentialManagementSubCommand subcommand;
base::Optional<cbor::Value::MapValue> params;
- base::Optional<std::array<uint8_t, 16>> pin_auth;
-
- private:
- CredentialManagementRequest() = delete;
- CredentialManagementRequest(Version version,
- CredentialManagementSubCommand subcommand,
- base::Optional<cbor::Value::MapValue> params,
- base::Optional<std::array<uint8_t, 16>> pin_auth);
- CredentialManagementRequest(const CredentialManagementRequest&) = delete;
- CredentialManagementRequest& operator=(const CredentialManagementRequest&) =
- delete;
+ base::Optional<std::vector<uint8_t>> pin_auth;
};
struct CredentialsMetadataResponse {
diff --git a/chromium/device/fido/credential_management_handler.cc b/chromium/device/fido/credential_management_handler.cc
index 8d8c6fa44bc..409bb4c1fff 100644
--- a/chromium/device/fido/credential_management_handler.cc
+++ b/chromium/device/fido/credential_management_handler.cc
@@ -157,7 +157,7 @@ void CredentialManagementHandler::OnHavePINToken(
}
state_ = State::kReady;
- pin_token_ = response->token();
+ pin_token_ = response;
std::move(ready_callback_).Run();
}
diff --git a/chromium/device/fido/credential_management_handler.h b/chromium/device/fido/credential_management_handler.h
index 757f3991879..63424609649 100644
--- a/chromium/device/fido/credential_management_handler.h
+++ b/chromium/device/fido/credential_management_handler.h
@@ -127,7 +127,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) CredentialManagementHandler
State state_ = State::kWaitingForTouch;
FidoAuthenticator* authenticator_ = nullptr;
- base::Optional<std::vector<uint8_t>> pin_token_;
+ base::Optional<pin::TokenResponse> pin_token_;
ReadyCallback ready_callback_;
GetPINCallback get_pin_callback_;
diff --git a/chromium/device/fido/ctap_get_assertion_request.h b/chromium/device/fido/ctap_get_assertion_request.h
index 3e777f27aa0..bec63882032 100644
--- a/chromium/device/fido/ctap_get_assertion_request.h
+++ b/chromium/device/fido/ctap_get_assertion_request.h
@@ -19,6 +19,7 @@
#include "device/fido/cable/cable_discovery_data.h"
#include "device/fido/client_data.h"
#include "device/fido/fido_constants.h"
+#include "device/fido/large_blob.h"
#include "device/fido/pin.h"
#include "device/fido/public_key_credential_descriptor.h"
@@ -57,6 +58,10 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) CtapGetAssertionOptions {
// it will be the first element and all others will have |credential_id|s.
// Elements are sorted by |credential_id|s, where present.
std::vector<PRFInput> prf_inputs;
+
+ // large_blob_operation indicates whether we should attempt to read or write a
+ // large blob after a successful assertion.
+ LargeBlobOperation large_blob_operation;
};
// Object that encapsulates request parameters for AuthenticatorGetAssertion as
@@ -121,6 +126,7 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) CtapGetAssertionRequest {
base::Optional<std::array<uint8_t, crypto::kSHA256Length>>
alternative_application_parameter;
base::Optional<HMACSecret> hmac_secret;
+ bool large_blob_key = false;
bool is_incognito_mode = false;
bool is_u2f_only = false;
diff --git a/chromium/device/fido/ctap_make_credential_request.cc b/chromium/device/fido/ctap_make_credential_request.cc
index bce0ccf50d6..0b78bf9bbb9 100644
--- a/chromium/device/fido/ctap_make_credential_request.cc
+++ b/chromium/device/fido/ctap_make_credential_request.cc
@@ -156,6 +156,11 @@ base::Optional<CtapMakeCredentialRequest> CtapMakeCredentialRequest::Parse(
return base::nullopt;
}
request.android_client_data_ext = std::move(*android_client_data_ext);
+ } else if (extension_name == kExtensionLargeBlobKey) {
+ if (!extension.second.is_bool() || !extension.second.GetBool()) {
+ return base::nullopt;
+ }
+ request.large_blob_key = true;
}
}
}
@@ -272,6 +277,10 @@ AsCTAPRequestValuePair(const CtapMakeCredentialRequest& request) {
extensions[cbor::Value(kExtensionHmacSecret)] = cbor::Value(true);
}
+ if (request.large_blob_key) {
+ extensions[cbor::Value(kExtensionLargeBlobKey)] = cbor::Value(true);
+ }
+
if (request.cred_protect) {
extensions.emplace(kExtensionCredProtect,
static_cast<int64_t>(*request.cred_protect));
diff --git a/chromium/device/fido/ctap_make_credential_request.h b/chromium/device/fido/ctap_make_credential_request.h
index 42dba7dfa84..bb88af3ce21 100644
--- a/chromium/device/fido/ctap_make_credential_request.h
+++ b/chromium/device/fido/ctap_make_credential_request.h
@@ -72,9 +72,12 @@ struct COMPONENT_EXPORT(DEVICE_FIDO) CtapMakeCredentialRequest {
AuthenticatorAttachment authenticator_attachment =
AuthenticatorAttachment::kAny;
bool resident_key_required = false;
- // hmac_secret_ indicates whether the "hmac-secret" extension should be
+ // hmac_secret indicates whether the "hmac-secret" extension should be
// asserted to CTAP2 authenticators.
bool hmac_secret = false;
+ // large_blob_key indicates whether a large blob key should be associated to
+ // the new credential through the "largeBlobKey" extension.
+ bool large_blob_key = false;
// If true, instruct the request handler only to dispatch this request via
// U2F.
diff --git a/chromium/device/fido/device_response_converter.cc b/chromium/device/fido/device_response_converter.cc
index 417de7f8a0d..c2ea71f5831 100644
--- a/chromium/device/fido/device_response_converter.cc
+++ b/chromium/device/fido/device_response_converter.cc
@@ -40,13 +40,14 @@ ProtocolVersion ConvertStringToProtocolVersion(base::StringPiece version) {
return ProtocolVersion::kUnknown;
}
-Ctap2Version ConvertStringToCtap2Version(base::StringPiece version) {
+base::Optional<Ctap2Version> ConvertStringToCtap2Version(
+ base::StringPiece version) {
if (version == kCtap2Version)
return Ctap2Version::kCtap2_0;
if (version == kCtap2_1Version)
return Ctap2Version::kCtap2_1;
- return Ctap2Version::kUnknown;
+ return base::nullopt;
}
// Converts a CBOR unsigned integer value to a uint32_t. The conversion is
@@ -67,7 +68,7 @@ CtapDeviceResponseCode GetResponseCode(base::span<const uint8_t> buffer) {
return CtapDeviceResponseCode::kCtap2ErrInvalidCBOR;
auto code = static_cast<CtapDeviceResponseCode>(buffer[0]);
- return base::Contains(GetCtapResponseCodeList(), code)
+ return base::Contains(kCtapResponseCodeList, code)
? code
: CtapDeviceResponseCode::kCtap2ErrInvalidCBOR;
}
@@ -120,6 +121,16 @@ ReadCTAPMakeCredentialResponse(FidoTransportProtocol transport_used,
}
}
+ it = decoded_map.find(CBOR(5));
+ if (it != decoded_map.end()) {
+ if (!it->second.is_bytestring() ||
+ it->second.GetBytestring().size() != kLargeBlobKeyLength) {
+ return base::nullopt;
+ }
+ response.set_large_blob_key(
+ base::make_span<kLargeBlobKeyLength>(it->second.GetBytestring()));
+ }
+
return response;
}
@@ -179,6 +190,16 @@ base::Optional<AuthenticatorGetAssertionResponse> ReadCTAPGetAssertionResponse(
}
}
+ it = response_map.find(CBOR(0x0B));
+ if (it != response_map.end()) {
+ if (!it->second.is_bytestring() ||
+ it->second.GetBytestring().size() != kLargeBlobKeyLength) {
+ return base::nullopt;
+ }
+ response.set_large_blob_key(
+ base::make_span<kLargeBlobKeyLength>(it->second.GetBytestring()));
+ }
+
return response;
}
@@ -224,21 +245,28 @@ base::Optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse(
return base::nullopt;
}
- auto protocol = ConvertStringToProtocolVersion(version_string);
+ ProtocolVersion protocol = ConvertStringToProtocolVersion(version_string);
if (protocol == ProtocolVersion::kUnknown) {
FIDO_LOG(DEBUG) << "Unexpected protocol version received.";
continue;
}
if (protocol == ProtocolVersion::kCtap2) {
- ctap2_versions.insert(ConvertStringToCtap2Version(version_string));
+ base::Optional<Ctap2Version> ctap2_version =
+ ConvertStringToCtap2Version(version_string);
+ if (ctap2_version) {
+ ctap2_versions.insert(*ctap2_version);
+ }
}
protocol_versions.insert(protocol);
}
- if (protocol_versions.empty())
+ if (protocol_versions.empty() ||
+ (base::Contains(protocol_versions, ProtocolVersion::kCtap2) &&
+ ctap2_versions.empty())) {
return base::nullopt;
+ }
it = response_map.find(CBOR(3));
if (it == response_map.end() || !it->second.is_bytestring() ||
@@ -404,6 +432,14 @@ base::Optional<AuthenticatorGetInfoResponse> ReadCTAPGetInfoResponse(
options.enterprise_attestation = option_map_it->second.GetBool();
}
+ option_map_it = option_map.find(CBOR(kLargeBlobsKey));
+ if (option_map_it != option_map.end()) {
+ if (!option_map_it->second.is_bool() || !options.supports_resident_key) {
+ return base::nullopt;
+ }
+ options.supports_large_blobs = option_map_it->second.GetBool();
+ }
+
response.options = std::move(options);
}
diff --git a/chromium/device/fido/fake_fido_discovery.cc b/chromium/device/fido/fake_fido_discovery.cc
index 46dd2cd9af9..952760c926d 100644
--- a/chromium/device/fido/fake_fido_discovery.cc
+++ b/chromium/device/fido/fake_fido_discovery.cc
@@ -79,23 +79,23 @@ FakeFidoDiscovery* FakeFidoDiscoveryFactory::ForgeNextPlatformDiscovery(
return next_platform_discovery_.get();
}
-std::unique_ptr<FidoDiscoveryBase> FakeFidoDiscoveryFactory::Create(
- FidoTransportProtocol transport) {
+std::vector<std::unique_ptr<FidoDiscoveryBase>>
+FakeFidoDiscoveryFactory::Create(FidoTransportProtocol transport) {
switch (transport) {
case FidoTransportProtocol::kUsbHumanInterfaceDevice:
- return std::move(next_hid_discovery_);
+ return SingleDiscovery(std::move(next_hid_discovery_));
case FidoTransportProtocol::kNearFieldCommunication:
- return std::move(next_nfc_discovery_);
+ return SingleDiscovery(std::move(next_nfc_discovery_));
case FidoTransportProtocol::kBluetoothLowEnergy:
case FidoTransportProtocol::kAndroidAccessory:
- return nullptr;
+ return {};
case FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy:
- return std::move(next_cable_discovery_);
+ return SingleDiscovery(std::move(next_cable_discovery_));
case FidoTransportProtocol::kInternal:
- return std::move(next_platform_discovery_);
+ return SingleDiscovery(std::move(next_platform_discovery_));
}
NOTREACHED();
- return nullptr;
+ return {};
}
} // namespace test
diff --git a/chromium/device/fido/fake_fido_discovery.h b/chromium/device/fido/fake_fido_discovery.h
index 365db9ccfa6..ee5aa1f9813 100644
--- a/chromium/device/fido/fake_fido_discovery.h
+++ b/chromium/device/fido/fake_fido_discovery.h
@@ -111,7 +111,7 @@ class FakeFidoDiscoveryFactory : public device::FidoDiscoveryFactory {
StartMode mode = StartMode::kManual);
// device::FidoDiscoveryFactory:
- std::unique_ptr<FidoDiscoveryBase> Create(
+ std::vector<std::unique_ptr<FidoDiscoveryBase>> Create(
FidoTransportProtocol transport) override;
private:
diff --git a/chromium/device/fido/fake_fido_discovery_unittest.cc b/chromium/device/fido/fake_fido_discovery_unittest.cc
index 670db181ab6..6161e32e283 100644
--- a/chromium/device/fido/fake_fido_discovery_unittest.cc
+++ b/chromium/device/fido/fake_fido_discovery_unittest.cc
@@ -147,10 +147,11 @@ TEST_F(FakeFidoDiscoveryFactoryTest, ForgesUsbFactoryFunction) {
fake_fido_discovery_factory_.ForgeNextHidDiscovery();
ASSERT_EQ(FidoTransportProtocol::kUsbHumanInterfaceDevice,
injected_fake_discovery->transport());
- auto produced_discovery = fake_fido_discovery_factory_.Create(
- FidoTransportProtocol::kUsbHumanInterfaceDevice);
- EXPECT_TRUE(produced_discovery);
- EXPECT_EQ(injected_fake_discovery, produced_discovery.get());
+ std::vector<std::unique_ptr<FidoDiscoveryBase>> produced_discoveries =
+ fake_fido_discovery_factory_.Create(
+ FidoTransportProtocol::kUsbHumanInterfaceDevice);
+ ASSERT_EQ(produced_discoveries.size(), 1u);
+ EXPECT_EQ(injected_fake_discovery, produced_discoveries[0].get());
}
#endif
diff --git a/chromium/device/fido/fido_authenticator.cc b/chromium/device/fido/fido_authenticator.cc
index 653b48f0c42..8f442c9b931 100644
--- a/chromium/device/fido/fido_authenticator.cc
+++ b/chromium/device/fido/fido_authenticator.cc
@@ -73,19 +73,19 @@ FidoAuthenticator::WillNeedPINToGetAssertion(
}
void FidoAuthenticator::GetCredentialsMetadata(
- base::span<const uint8_t> pin_token,
+ const pin::TokenResponse& pin_token,
GetCredentialsMetadataCallback callback) {
NOTREACHED();
}
void FidoAuthenticator::EnumerateCredentials(
- base::span<const uint8_t> pin_token,
+ const pin::TokenResponse& pin_token,
EnumerateCredentialsCallback callback) {
NOTREACHED();
}
void FidoAuthenticator::DeleteCredential(
- base::span<const uint8_t> pin_token,
+ const pin::TokenResponse& pin_token,
const PublicKeyCredentialDescriptor& credential_id,
DeleteCredentialCallback callback) {
NOTREACHED();
@@ -128,6 +128,21 @@ void FidoAuthenticator::BioEnrollDelete(const pin::TokenResponse&,
NOTREACHED();
}
+void FidoAuthenticator::WriteLargeBlob(
+ const std::vector<uint8_t>& large_blob,
+ const LargeBlobKey& large_blob_key,
+ const base::Optional<pin::TokenResponse> pin_uv_auth_token,
+ base::OnceCallback<void(CtapDeviceResponseCode)> callback) {
+ NOTREACHED();
+}
+
+void FidoAuthenticator::ReadLargeBlob(
+ const std::vector<LargeBlobKey>& large_blob_keys,
+ const base::Optional<pin::TokenResponse> pin_uv_auth_token,
+ LargeBlobReadCallback callback) {
+ NOTREACHED();
+}
+
base::Optional<base::span<const int32_t>> FidoAuthenticator::GetAlgorithms() {
return base::nullopt;
}
diff --git a/chromium/device/fido/fido_authenticator.h b/chromium/device/fido/fido_authenticator.h
index ad984032d61..5154563046e 100644
--- a/chromium/device/fido/fido_authenticator.h
+++ b/chromium/device/fido/fido_authenticator.h
@@ -5,6 +5,7 @@
#ifndef DEVICE_FIDO_FIDO_AUTHENTICATOR_H_
#define DEVICE_FIDO_FIDO_AUTHENTICATOR_H_
+#include <cstdint>
#include <string>
#include "base/callback_forward.h"
@@ -20,8 +21,10 @@
#include "device/fido/authenticator_supported_options.h"
#include "device/fido/bio/enrollment.h"
#include "device/fido/credential_management.h"
+#include "device/fido/fido_constants.h"
#include "device/fido/fido_request_handler_base.h"
#include "device/fido/fido_transport_protocol.h"
+#include "device/fido/large_blob.h"
namespace device {
@@ -70,6 +73,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoAuthenticator {
using BioEnrollmentCallback =
base::OnceCallback<void(CtapDeviceResponseCode,
base::Optional<BioEnrollmentResponse>)>;
+ using LargeBlobReadCallback = base::OnceCallback<void(
+ CtapDeviceResponseCode,
+ base::Optional<std::vector<std::pair<LargeBlobKey, std::vector<uint8_t>>>>
+ callback)>;
FidoAuthenticator() = default;
virtual ~FidoAuthenticator() = default;
@@ -175,12 +182,12 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoAuthenticator {
const CtapGetAssertionRequest& request,
const FidoRequestHandlerBase::Observer* observer);
- virtual void GetCredentialsMetadata(base::span<const uint8_t> pin_token,
+ virtual void GetCredentialsMetadata(const pin::TokenResponse& pin_token,
GetCredentialsMetadataCallback callback);
- virtual void EnumerateCredentials(base::span<const uint8_t> pin_token,
+ virtual void EnumerateCredentials(const pin::TokenResponse& pin_token,
EnumerateCredentialsCallback callback);
virtual void DeleteCredential(
- base::span<const uint8_t> pin_token,
+ const pin::TokenResponse& pin_token,
const PublicKeyCredentialDescriptor& credential_id,
DeleteCredentialCallback callback);
@@ -202,6 +209,21 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoAuthenticator {
std::vector<uint8_t> template_id,
BioEnrollmentCallback);
+ // Large blob commands.
+ // Attempts to write a |large_blob| into the credential. If there is an
+ // existing credential for the |large_blob_key|, it will be overwritten.
+ virtual void WriteLargeBlob(
+ const std::vector<uint8_t>& large_blob,
+ const LargeBlobKey& large_blob_key,
+ base::Optional<pin::TokenResponse> pin_uv_auth_token,
+ base::OnceCallback<void(CtapDeviceResponseCode)> callback);
+ // Attempts to read large blobs from the credential encrypted with
+ // |large_blob_keys|. Returns a map of keys to their blobs.
+ virtual void ReadLargeBlob(
+ const std::vector<LargeBlobKey>& large_blob_keys,
+ base::Optional<pin::TokenResponse> pin_uv_auth_token,
+ LargeBlobReadCallback callback);
+
// GetAlgorithms returns the list of supported COSEAlgorithmIdentifiers, or
// |nullopt| if this is unknown and thus all requests should be tried in case
// they work.
diff --git a/chromium/device/fido/fido_constants.cc b/chromium/device/fido/fido_constants.cc
index 47f1d3d66ba..84b41eae320 100644
--- a/chromium/device/fido/fido_constants.cc
+++ b/chromium/device/fido/fido_constants.cc
@@ -36,6 +36,7 @@ const char kBioEnrollmentPreviewMapKey[] = "userVerificationMgmtPreview";
const char kPinUvTokenMapKey[] = "pinUvAuthToken";
const char kDefaultCredProtectKey[] = "defaultCredProtect";
const char kEnterpriseAttestationKey[] = "ep";
+const char kLargeBlobsKey[] = "largeBlobs";
const base::TimeDelta kDeviceTimeout = base::TimeDelta::FromSeconds(20);
const base::TimeDelta kU2fRetryDelay = base::TimeDelta::FromMilliseconds(200);
@@ -71,6 +72,7 @@ const char kCtap2_1Version[] = "FIDO_2_1";
const char kExtensionHmacSecret[] = "hmac-secret";
const char kExtensionCredProtect[] = "credProtect";
const char kExtensionAndroidClientData[] = "googleAndroidClientData";
+const char kExtensionLargeBlobKey[] = "largeBlobKey";
const base::TimeDelta kBleDevicePairingModeWaitingInterval =
base::TimeDelta::FromSeconds(2);
diff --git a/chromium/device/fido/fido_constants.h b/chromium/device/fido/fido_constants.h
index e9d5e833177..043d4fceb8b 100644
--- a/chromium/device/fido/fido_constants.h
+++ b/chromium/device/fido/fido_constants.h
@@ -40,6 +40,10 @@ constexpr size_t kClientDataHashLength = 32;
// https://www.w3.org/TR/webauthn/#sec-authenticator-data
constexpr size_t kRpIdHashLength = 32;
+// Length of the key used to encrypt large blobs.
+// TODO(nsatragno): add a link to the spec once it's published.
+constexpr size_t kLargeBlobKeyLength = 32;
+
// Max length for the user handle:
// https://www.w3.org/TR/webauthn/#user-handle
constexpr size_t kUserHandleMaxLength = 64;
@@ -84,7 +88,7 @@ enum class CtapDeviceResponseCode : uint8_t {
kCtap2ErrLimitExceeded = 0x15,
kCtap2ErrUnsupportedExtension = 0x16,
kCtap2ErrTooManyElements = 0x17,
- kCtap2ErrExtensionNotSupported = 0x18,
+ kCtap2ErrLargeBlobStorageFull = 0x18,
kCtap2ErrCredentialExcluded = 0x19,
kCtap2ErrProcesssing = 0x21,
kCtap2ErrInvalidCredential = 0x22,
@@ -112,6 +116,8 @@ enum class CtapDeviceResponseCode : uint8_t {
kCtap2ErrPinTokenExpired = 0x38,
kCtap2ErrRequestTooLarge = 0x39,
kCtap2ErrUvBlocked = 0x3C,
+ kCtap2ErrIntegrityFailure = 0x3D,
+ kCtap2ErrUvInvalid = 0x3F,
kCtap2ErrOther = 0x7F,
kCtap2ErrSpecLast = 0xDF,
kCtap2ErrExtensionFirst = 0xE0,
@@ -120,57 +126,59 @@ enum class CtapDeviceResponseCode : uint8_t {
kCtap2ErrVendorLast = 0xFF
};
-constexpr std::array<CtapDeviceResponseCode, 49> GetCtapResponseCodeList() {
- return {CtapDeviceResponseCode::kSuccess,
- CtapDeviceResponseCode::kCtap1ErrInvalidCommand,
- CtapDeviceResponseCode::kCtap1ErrInvalidParameter,
- CtapDeviceResponseCode::kCtap1ErrInvalidLength,
- CtapDeviceResponseCode::kCtap1ErrInvalidSeq,
- CtapDeviceResponseCode::kCtap1ErrTimeout,
- CtapDeviceResponseCode::kCtap1ErrChannelBusy,
- CtapDeviceResponseCode::kCtap1ErrLockRequired,
- CtapDeviceResponseCode::kCtap1ErrInvalidChannel,
- CtapDeviceResponseCode::kCtap2ErrCBORUnexpectedType,
- CtapDeviceResponseCode::kCtap2ErrInvalidCBOR,
- CtapDeviceResponseCode::kCtap2ErrMissingParameter,
- CtapDeviceResponseCode::kCtap2ErrLimitExceeded,
- CtapDeviceResponseCode::kCtap2ErrUnsupportedExtension,
- CtapDeviceResponseCode::kCtap2ErrTooManyElements,
- CtapDeviceResponseCode::kCtap2ErrExtensionNotSupported,
- CtapDeviceResponseCode::kCtap2ErrCredentialExcluded,
- CtapDeviceResponseCode::kCtap2ErrProcesssing,
- CtapDeviceResponseCode::kCtap2ErrInvalidCredential,
- CtapDeviceResponseCode::kCtap2ErrUserActionPending,
- CtapDeviceResponseCode::kCtap2ErrOperationPending,
- CtapDeviceResponseCode::kCtap2ErrNoOperations,
- CtapDeviceResponseCode::kCtap2ErrUnsupportedAlgorithm,
- CtapDeviceResponseCode::kCtap2ErrOperationDenied,
- CtapDeviceResponseCode::kCtap2ErrKeyStoreFull,
- CtapDeviceResponseCode::kCtap2ErrNotBusy,
- CtapDeviceResponseCode::kCtap2ErrNoOperationPending,
- CtapDeviceResponseCode::kCtap2ErrUnsupportedOption,
- CtapDeviceResponseCode::kCtap2ErrInvalidOption,
- CtapDeviceResponseCode::kCtap2ErrKeepAliveCancel,
- CtapDeviceResponseCode::kCtap2ErrNoCredentials,
- CtapDeviceResponseCode::kCtap2ErrUserActionTimeout,
- CtapDeviceResponseCode::kCtap2ErrNotAllowed,
- CtapDeviceResponseCode::kCtap2ErrPinInvalid,
- CtapDeviceResponseCode::kCtap2ErrPinBlocked,
- CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid,
- CtapDeviceResponseCode::kCtap2ErrPinAuthBlocked,
- CtapDeviceResponseCode::kCtap2ErrPinNotSet,
- CtapDeviceResponseCode::kCtap2ErrPinRequired,
- CtapDeviceResponseCode::kCtap2ErrPinPolicyViolation,
- CtapDeviceResponseCode::kCtap2ErrPinTokenExpired,
- CtapDeviceResponseCode::kCtap2ErrRequestTooLarge,
- CtapDeviceResponseCode::kCtap2ErrUvBlocked,
- CtapDeviceResponseCode::kCtap2ErrOther,
- CtapDeviceResponseCode::kCtap2ErrSpecLast,
- CtapDeviceResponseCode::kCtap2ErrExtensionFirst,
- CtapDeviceResponseCode::kCtap2ErrExtensionLast,
- CtapDeviceResponseCode::kCtap2ErrVendorFirst,
- CtapDeviceResponseCode::kCtap2ErrVendorLast};
-}
+constexpr std::array<CtapDeviceResponseCode, 51> kCtapResponseCodeList{
+ CtapDeviceResponseCode::kSuccess,
+ CtapDeviceResponseCode::kCtap1ErrInvalidCommand,
+ CtapDeviceResponseCode::kCtap1ErrInvalidParameter,
+ CtapDeviceResponseCode::kCtap1ErrInvalidLength,
+ CtapDeviceResponseCode::kCtap1ErrInvalidSeq,
+ CtapDeviceResponseCode::kCtap1ErrTimeout,
+ CtapDeviceResponseCode::kCtap1ErrChannelBusy,
+ CtapDeviceResponseCode::kCtap1ErrLockRequired,
+ CtapDeviceResponseCode::kCtap1ErrInvalidChannel,
+ CtapDeviceResponseCode::kCtap2ErrCBORUnexpectedType,
+ CtapDeviceResponseCode::kCtap2ErrInvalidCBOR,
+ CtapDeviceResponseCode::kCtap2ErrMissingParameter,
+ CtapDeviceResponseCode::kCtap2ErrLimitExceeded,
+ CtapDeviceResponseCode::kCtap2ErrUnsupportedExtension,
+ CtapDeviceResponseCode::kCtap2ErrTooManyElements,
+ CtapDeviceResponseCode::kCtap2ErrLargeBlobStorageFull,
+ CtapDeviceResponseCode::kCtap2ErrCredentialExcluded,
+ CtapDeviceResponseCode::kCtap2ErrProcesssing,
+ CtapDeviceResponseCode::kCtap2ErrInvalidCredential,
+ CtapDeviceResponseCode::kCtap2ErrUserActionPending,
+ CtapDeviceResponseCode::kCtap2ErrOperationPending,
+ CtapDeviceResponseCode::kCtap2ErrNoOperations,
+ CtapDeviceResponseCode::kCtap2ErrUnsupportedAlgorithm,
+ CtapDeviceResponseCode::kCtap2ErrOperationDenied,
+ CtapDeviceResponseCode::kCtap2ErrKeyStoreFull,
+ CtapDeviceResponseCode::kCtap2ErrNotBusy,
+ CtapDeviceResponseCode::kCtap2ErrNoOperationPending,
+ CtapDeviceResponseCode::kCtap2ErrUnsupportedOption,
+ CtapDeviceResponseCode::kCtap2ErrInvalidOption,
+ CtapDeviceResponseCode::kCtap2ErrKeepAliveCancel,
+ CtapDeviceResponseCode::kCtap2ErrNoCredentials,
+ CtapDeviceResponseCode::kCtap2ErrUserActionTimeout,
+ CtapDeviceResponseCode::kCtap2ErrNotAllowed,
+ CtapDeviceResponseCode::kCtap2ErrPinInvalid,
+ CtapDeviceResponseCode::kCtap2ErrPinBlocked,
+ CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid,
+ CtapDeviceResponseCode::kCtap2ErrPinAuthBlocked,
+ CtapDeviceResponseCode::kCtap2ErrPinNotSet,
+ CtapDeviceResponseCode::kCtap2ErrPinRequired,
+ CtapDeviceResponseCode::kCtap2ErrPinPolicyViolation,
+ CtapDeviceResponseCode::kCtap2ErrPinTokenExpired,
+ CtapDeviceResponseCode::kCtap2ErrRequestTooLarge,
+ CtapDeviceResponseCode::kCtap2ErrUvBlocked,
+ CtapDeviceResponseCode::kCtap2ErrIntegrityFailure,
+ CtapDeviceResponseCode::kCtap2ErrUvInvalid,
+ CtapDeviceResponseCode::kCtap2ErrOther,
+ CtapDeviceResponseCode::kCtap2ErrSpecLast,
+ CtapDeviceResponseCode::kCtap2ErrExtensionFirst,
+ CtapDeviceResponseCode::kCtap2ErrExtensionLast,
+ CtapDeviceResponseCode::kCtap2ErrVendorFirst,
+ CtapDeviceResponseCode::kCtap2ErrVendorLast,
+};
// Commands supported by CTAPHID device as specified in
// https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#ctaphid-commands
@@ -228,6 +236,7 @@ enum class CtapRequestCommand : uint8_t {
kAuthenticatorClientPin = 0x06,
kAuthenticatorReset = 0x07,
kAuthenticatorBioEnrollment = 0x09,
+ kAuthenticatorLargeBlobs = 0x0C,
kAuthenticatorBioEnrollmentPreview = 0x40,
kAuthenticatorCredentialManagement = 0x0a,
kAuthenticatorCredentialManagementPreview = 0x41,
@@ -325,6 +334,7 @@ COMPONENT_EXPORT(DEVICE_FIDO) extern const char kBioEnrollmentPreviewMapKey[];
COMPONENT_EXPORT(DEVICE_FIDO) extern const char kPinUvTokenMapKey[];
extern const char kDefaultCredProtectKey[];
extern const char kEnterpriseAttestationKey[];
+extern const char kLargeBlobsKey[];
// HID transport specific constants.
constexpr uint32_t kHidBroadcastChannel = 0xffffffff;
@@ -368,8 +378,19 @@ constexpr char kCableWebSocketProtocol[] = "fido.cable";
// kCableShardIdHeader is the name of an HTTP header that is sent in the reply
// from the tunnel server and which specifies the server's chosen shard number.
+// TODO(agl): remove. Only being kept around to allow things to compile.
constexpr char kCableShardIdHeader[] = "X-caBLE-Shard";
+// kCableRoutingIdHeader is the name of an HTTP header that is sent in the reply
+// from the tunnel server and which specifies the server's chosen routing ID
+// which other parties can use to reach the same tunnel server.
+constexpr char kCableRoutingIdHeader[] = "X-caBLE-Routing-ID";
+
+// kCableClientPayloadHeader is the name of an HTTP header that is to
+// the tunnel server when performing a state-assisted handshake and which
+// includes the client's nonce and pairing ID.
+constexpr char kCableClientPayloadHeader[] = "X-caBLE-Client-Payload";
+
// Maximum wait time before client error outs on device.
COMPONENT_EXPORT(DEVICE_FIDO) extern const base::TimeDelta kDeviceTimeout;
@@ -400,11 +421,11 @@ COMPONENT_EXPORT(DEVICE_FIDO)
extern const char kCableAuthenticatorHelloMessage[];
COMPONENT_EXPORT(DEVICE_FIDO) extern const char kCableClientHelloMessage[];
-enum class Ctap2Version {
- kUnknown = 0,
- kCtap2_0 = 1,
- kCtap2_1 = 2,
-};
+// The list of CTAP versions returned in the getInfo response for different
+// minor versions.
+constexpr Ctap2Version kCtap2Versions2_0[] = {Ctap2Version::kCtap2_0};
+constexpr Ctap2Version kCtap2Versions2_1[] = {Ctap2Version::kCtap2_0,
+ Ctap2Version::kCtap2_1};
// Protocol version strings.
// https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetInfo
@@ -419,6 +440,7 @@ COMPONENT_EXPORT(DEVICE_FIDO) extern const char kExtensionHmacSecret[];
COMPONENT_EXPORT(DEVICE_FIDO) extern const char kExtensionCredProtect[];
COMPONENT_EXPORT(DEVICE_FIDO)
extern const char kExtensionAndroidClientData[];
+COMPONENT_EXPORT(DEVICE_FIDO) extern const char kExtensionLargeBlobKey[];
// Maximum number of seconds the browser waits for Bluetooth authenticator to
// send packets that advertises that the device is in pairing mode before
diff --git a/chromium/device/fido/fido_device_authenticator.cc b/chromium/device/fido/fido_device_authenticator.cc
index bf6405e1cd7..0690ea31b66 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 <algorithm>
#include <numeric>
#include <utility>
@@ -16,9 +17,11 @@
#include "device/fido/ctap_get_assertion_request.h"
#include "device/fido/ctap_make_credential_request.h"
#include "device/fido/features.h"
+#include "device/fido/fido_constants.h"
#include "device/fido/fido_device.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/get_assertion_task.h"
+#include "device/fido/large_blob.h"
#include "device/fido/make_credential_task.h"
#include "device/fido/pin.h"
#include "device/fido/u2f_command_constructor.h"
@@ -338,7 +341,6 @@ FidoDeviceAuthenticator::WillNeedPINToMakeCredential(
: MakeCredentialPINDisposition::kNoPIN;
}
-
// 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 u2f_fallback_possible =
@@ -436,7 +438,7 @@ FidoDeviceAuthenticator::WillNeedPINToGetAssertion(
}
void FidoDeviceAuthenticator::GetCredentialsMetadata(
- base::span<const uint8_t> pin_token,
+ const pin::TokenResponse& pin_token,
GetCredentialsMetadataCallback callback) {
DCHECK(Options()->supports_credential_management ||
Options()->supports_credential_management_preview);
@@ -448,11 +450,12 @@ void FidoDeviceAuthenticator::GetCredentialsMetadata(
}
struct FidoDeviceAuthenticator::EnumerateCredentialsState {
- EnumerateCredentialsState() = default;
+ explicit EnumerateCredentialsState(pin::TokenResponse pin_token_)
+ : pin_token(pin_token_) {}
EnumerateCredentialsState(EnumerateCredentialsState&&) = default;
EnumerateCredentialsState& operator=(EnumerateCredentialsState&&) = default;
- std::vector<uint8_t> pin_token;
+ pin::TokenResponse pin_token;
bool is_first_rp = true;
bool is_first_credential = true;
size_t rp_count;
@@ -463,13 +466,12 @@ struct FidoDeviceAuthenticator::EnumerateCredentialsState {
};
void FidoDeviceAuthenticator::EnumerateCredentials(
- base::span<const uint8_t> pin_token,
+ const pin::TokenResponse& pin_token,
EnumerateCredentialsCallback callback) {
DCHECK(Options()->supports_credential_management ||
Options()->supports_credential_management_preview);
- EnumerateCredentialsState state;
- state.pin_token = fido_parsing_utils::Materialize(pin_token);
+ EnumerateCredentialsState state(pin_token);
state.callback = std::move(callback);
RunOperation<CredentialManagementRequest, EnumerateRPsResponse>(
CredentialManagementRequest::ForEnumerateRPsBegin(
@@ -626,7 +628,7 @@ void FidoDeviceAuthenticator::OnEnumerateCredentialsDone(
}
void FidoDeviceAuthenticator::DeleteCredential(
- base::span<const uint8_t> pin_token,
+ const pin::TokenResponse& pin_token,
const PublicKeyCredentialDescriptor& credential_id,
DeleteCredentialCallback callback) {
DCHECK(Options()->supports_credential_management ||
@@ -699,14 +701,201 @@ void FidoDeviceAuthenticator::BioEnrollCancel(BioEnrollmentCallback callback) {
}
void FidoDeviceAuthenticator::BioEnrollEnumerate(
- const pin::TokenResponse& response,
+ const pin::TokenResponse& pin_token,
BioEnrollmentCallback callback) {
RunOperation<BioEnrollmentRequest, BioEnrollmentResponse>(
BioEnrollmentRequest::ForEnumerate(
- GetBioEnrollmentRequestVersion(*Options()), std::move(response)),
+ GetBioEnrollmentRequestVersion(*Options()), std::move(pin_token)),
std::move(callback), base::BindOnce(&BioEnrollmentResponse::Parse));
}
+void FidoDeviceAuthenticator::WriteLargeBlob(
+ const std::vector<uint8_t>& large_blob,
+ const LargeBlobKey& large_blob_key,
+ const base::Optional<pin::TokenResponse> pin_uv_auth_token,
+ base::OnceCallback<void(CtapDeviceResponseCode)> callback) {
+ auto pin_uv_auth_token_copy = pin_uv_auth_token;
+ FetchLargeBlobArray(
+ pin_uv_auth_token_copy, LargeBlobArrayReader(),
+ base::BindOnce(&FidoDeviceAuthenticator::OnHaveLargeBlobArrayForWrite,
+ weak_factory_.GetWeakPtr(), large_blob, large_blob_key,
+ std::move(pin_uv_auth_token), std::move(callback)));
+}
+
+void FidoDeviceAuthenticator::ReadLargeBlob(
+ const std::vector<LargeBlobKey>& large_blob_keys,
+ const base::Optional<pin::TokenResponse> pin_uv_auth_token,
+ LargeBlobReadCallback callback) {
+ FetchLargeBlobArray(
+ std::move(pin_uv_auth_token), LargeBlobArrayReader(),
+ base::BindOnce(&FidoDeviceAuthenticator::OnHaveLargeBlobArrayForRead,
+ weak_factory_.GetWeakPtr(), large_blob_keys,
+ std::move(callback)));
+}
+
+void FidoDeviceAuthenticator::FetchLargeBlobArray(
+ const base::Optional<pin::TokenResponse> pin_uv_auth_token,
+ LargeBlobArrayReader large_blob_array_reader,
+ base::OnceCallback<void(CtapDeviceResponseCode,
+ base::Optional<LargeBlobArrayReader>)> callback) {
+ size_t bytes_to_read = max_large_blob_fragment_length();
+ LargeBlobsRequest request =
+ LargeBlobsRequest::ForRead(bytes_to_read, large_blob_array_reader.size());
+ if (pin_uv_auth_token) {
+ request.SetPinParam(*pin_uv_auth_token);
+ }
+ RunOperation<LargeBlobsRequest, LargeBlobsResponse>(
+ std::move(request),
+ base::BindOnce(&FidoDeviceAuthenticator::OnReadLargeBlobFragment,
+ weak_factory_.GetWeakPtr(), bytes_to_read,
+ std::move(large_blob_array_reader),
+ std::move(pin_uv_auth_token), std::move(callback)),
+ base::BindOnce(&LargeBlobsResponse::ParseForRead, bytes_to_read));
+}
+
+void FidoDeviceAuthenticator::OnReadLargeBlobFragment(
+ const size_t bytes_requested,
+ LargeBlobArrayReader large_blob_array_reader,
+ const base::Optional<pin::TokenResponse> pin_uv_auth_token,
+ base::OnceCallback<void(CtapDeviceResponseCode,
+ base::Optional<LargeBlobArrayReader>)> callback,
+ CtapDeviceResponseCode status,
+ base::Optional<LargeBlobsResponse> response) {
+ if (status != CtapDeviceResponseCode::kSuccess) {
+ std::move(callback).Run(status, base::nullopt);
+ return;
+ }
+
+ DCHECK(response && response->config());
+ large_blob_array_reader.Append(*response->config());
+
+ if (response->config()->size() == bytes_requested) {
+ // More data may be available, read the next fragment.
+ FetchLargeBlobArray(std::move(pin_uv_auth_token),
+ std::move(large_blob_array_reader),
+ std::move(callback));
+ return;
+ }
+
+ std::move(callback).Run(CtapDeviceResponseCode::kSuccess,
+ std::move(large_blob_array_reader));
+}
+
+void FidoDeviceAuthenticator::OnHaveLargeBlobArrayForWrite(
+ const std::vector<uint8_t>& large_blob,
+ const LargeBlobKey& large_blob_key,
+ const base::Optional<pin::TokenResponse> pin_uv_auth_token,
+ base::OnceCallback<void(CtapDeviceResponseCode)> callback,
+ CtapDeviceResponseCode status,
+ base::Optional<LargeBlobArrayReader> large_blob_array_reader) {
+ if (status != CtapDeviceResponseCode::kSuccess) {
+ std::move(callback).Run(status);
+ return;
+ }
+
+ base::Optional<std::vector<LargeBlobData>> large_blob_array =
+ large_blob_array_reader->Materialize();
+ if (!large_blob_array) {
+ // The large blob array is corrupted. Replace it completely with a new one.
+ // TODO(nsatragno): but maybe we want to do something else like trying
+ // again? It might have been corrupted while transported. Decide when we
+ // have hardware to test.
+ large_blob_array.emplace();
+ return;
+ }
+
+ auto existing_large_blob =
+ std::find_if(large_blob_array->begin(), large_blob_array->end(),
+ [&large_blob_key](const LargeBlobData& blob) {
+ return blob.Decrypt(large_blob_key);
+ });
+
+ LargeBlobData new_large_blob_data(large_blob_key, large_blob);
+ if (existing_large_blob != large_blob_array->end()) {
+ *existing_large_blob = std::move(new_large_blob_data);
+ } else {
+ large_blob_array->emplace_back(std::move(new_large_blob_data));
+ }
+
+ WriteLargeBlobArray(std::move(pin_uv_auth_token),
+ LargeBlobArrayWriter(*large_blob_array),
+ std::move(callback));
+}
+
+void FidoDeviceAuthenticator::WriteLargeBlobArray(
+ const base::Optional<pin::TokenResponse> pin_uv_auth_token,
+ LargeBlobArrayWriter large_blob_array_writer,
+ base::OnceCallback<void(CtapDeviceResponseCode)> callback) {
+ LargeBlobArrayFragment fragment =
+ large_blob_array_writer.Pop(max_large_blob_fragment_length());
+
+ LargeBlobsRequest request = LargeBlobsRequest::ForWrite(
+ std::move(fragment), large_blob_array_writer.size());
+ if (pin_uv_auth_token) {
+ request.SetPinParam(*pin_uv_auth_token);
+ }
+ RunOperation<LargeBlobsRequest, LargeBlobsResponse>(
+ std::move(request),
+ base::BindOnce(&FidoDeviceAuthenticator::OnWriteLargeBlobFragment,
+ weak_factory_.GetWeakPtr(),
+ std::move(large_blob_array_writer),
+ std::move(pin_uv_auth_token), std::move(callback)),
+ base::BindOnce(&LargeBlobsResponse::ParseForWrite));
+}
+
+void FidoDeviceAuthenticator::OnWriteLargeBlobFragment(
+ LargeBlobArrayWriter large_blob_array_writer,
+ const base::Optional<pin::TokenResponse> pin_uv_auth_token,
+ base::OnceCallback<void(CtapDeviceResponseCode)> callback,
+ CtapDeviceResponseCode status,
+ base::Optional<LargeBlobsResponse> response) {
+ if (status != CtapDeviceResponseCode::kSuccess) {
+ std::move(callback).Run(status);
+ return;
+ }
+
+ if (large_blob_array_writer.has_remaining_fragments()) {
+ WriteLargeBlobArray(std::move(pin_uv_auth_token),
+ std::move(large_blob_array_writer),
+ std::move(callback));
+ return;
+ }
+
+ std::move(callback).Run(CtapDeviceResponseCode::kSuccess);
+}
+
+void FidoDeviceAuthenticator::OnHaveLargeBlobArrayForRead(
+ const std::vector<LargeBlobKey>& large_blob_keys,
+ LargeBlobReadCallback callback,
+ CtapDeviceResponseCode status,
+ base::Optional<LargeBlobArrayReader> large_blob_array_reader) {
+ if (status != CtapDeviceResponseCode::kSuccess) {
+ std::move(callback).Run(status, base::nullopt);
+ return;
+ }
+
+ base::Optional<std::vector<LargeBlobData>> large_blob_array =
+ large_blob_array_reader->Materialize();
+ if (!large_blob_array) {
+ std::move(callback).Run(CtapDeviceResponseCode::kCtap2ErrIntegrityFailure,
+ base::nullopt);
+ return;
+ }
+
+ std::vector<std::pair<LargeBlobKey, std::vector<uint8_t>>> result;
+ for (const LargeBlobData& blob : *large_blob_array) {
+ for (const LargeBlobKey& key : large_blob_keys) {
+ base::Optional<std::vector<uint8_t>> plaintext = blob.Decrypt(key);
+ if (plaintext) {
+ result.emplace_back(std::make_pair(key, std::move(*plaintext)));
+ break;
+ }
+ }
+ }
+
+ std::move(callback).Run(CtapDeviceResponseCode::kSuccess, std::move(result));
+}
+
base::Optional<base::span<const int32_t>>
FidoDeviceAuthenticator::GetAlgorithms() {
if (device_->supported_protocol() == ProtocolVersion::kU2f) {
@@ -860,6 +1049,13 @@ void FidoDeviceAuthenticator::OnHaveEphemeralKeyForUvToken(
base::BindOnce(&pin::TokenResponse::Parse, std::move(shared_key)));
}
+size_t FidoDeviceAuthenticator::max_large_blob_fragment_length() {
+ return device_->device_info()->max_msg_size
+ ? *device_->device_info()->max_msg_size -
+ kLargeBlobReadEncodingOverhead
+ : kLargeBlobDefaultMaxFragmentLength;
+}
+
base::WeakPtr<FidoAuthenticator> FidoDeviceAuthenticator::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
diff --git a/chromium/device/fido/fido_device_authenticator.h b/chromium/device/fido/fido_device_authenticator.h
index 771df599a00..06e875ba5cc 100644
--- a/chromium/device/fido/fido_device_authenticator.h
+++ b/chromium/device/fido/fido_device_authenticator.h
@@ -17,7 +17,10 @@
#include "build/build_config.h"
#include "device/fido/ctap2_device_operation.h"
#include "device/fido/fido_authenticator.h"
+#include "device/fido/fido_constants.h"
#include "device/fido/fido_request_handler_base.h"
+#include "device/fido/large_blob.h"
+#include "device/fido/pin.h"
namespace device {
@@ -70,11 +73,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator
const CtapGetAssertionRequest& request,
const FidoRequestHandlerBase::Observer* observer) override;
- void GetCredentialsMetadata(base::span<const uint8_t> pin_token,
+ void GetCredentialsMetadata(const pin::TokenResponse& pin_token,
GetCredentialsMetadataCallback callback) override;
- void EnumerateCredentials(base::span<const uint8_t> pin_token,
+ void EnumerateCredentials(const pin::TokenResponse& pin_token,
EnumerateCredentialsCallback callback) override;
- void DeleteCredential(base::span<const uint8_t> pin_token,
+ void DeleteCredential(const pin::TokenResponse& pin_token,
const PublicKeyCredentialDescriptor& credential_id,
DeleteCredentialCallback callback) override;
@@ -93,6 +96,14 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator
void BioEnrollDelete(const pin::TokenResponse&,
std::vector<uint8_t> template_id,
BioEnrollmentCallback) override;
+ void WriteLargeBlob(
+ const std::vector<uint8_t>& large_blob,
+ const LargeBlobKey& large_blob_key,
+ base::Optional<pin::TokenResponse> pin_uv_auth_token,
+ base::OnceCallback<void(CtapDeviceResponseCode)> callback) override;
+ void ReadLargeBlob(const std::vector<LargeBlobKey>& large_blob_keys,
+ base::Optional<pin::TokenResponse> pin_uv_auth_token,
+ LargeBlobReadCallback callback) override;
base::Optional<base::span<const int32_t>> GetAlgorithms() override;
void Reset(ResetCallback callback) override;
@@ -168,6 +179,42 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator
CtapDeviceResponseCode status,
base::Optional<pin::KeyAgreementResponse> key);
+ void FetchLargeBlobArray(
+ base::Optional<pin::TokenResponse> pin_uv_auth_token,
+ LargeBlobArrayReader large_blob_array_reader,
+ base::OnceCallback<void(CtapDeviceResponseCode,
+ base::Optional<LargeBlobArrayReader>)> callback);
+ void WriteLargeBlobArray(
+ base::Optional<pin::TokenResponse> pin_uv_auth_token,
+ LargeBlobArrayWriter large_blob_array_writer,
+ base::OnceCallback<void(CtapDeviceResponseCode)> callback);
+ void OnReadLargeBlobFragment(
+ const size_t bytes_requested,
+ LargeBlobArrayReader large_blob_array_reader,
+ base::Optional<pin::TokenResponse> pin_uv_auth_token,
+ base::OnceCallback<void(CtapDeviceResponseCode,
+ base::Optional<LargeBlobArrayReader>)> callback,
+ CtapDeviceResponseCode status,
+ base::Optional<LargeBlobsResponse> response);
+ void OnWriteLargeBlobFragment(
+ LargeBlobArrayWriter large_blob_array_writer,
+ base::Optional<pin::TokenResponse> pin_uv_auth_token,
+ base::OnceCallback<void(CtapDeviceResponseCode)> callback,
+ CtapDeviceResponseCode status,
+ base::Optional<LargeBlobsResponse> response);
+ void OnHaveLargeBlobArrayForWrite(
+ const std::vector<uint8_t>& large_blob,
+ const LargeBlobKey& large_blob_key,
+ base::Optional<pin::TokenResponse> pin_uv_auth_token,
+ base::OnceCallback<void(CtapDeviceResponseCode)> callback,
+ CtapDeviceResponseCode status,
+ base::Optional<LargeBlobArrayReader> large_blob_array_reader);
+ void OnHaveLargeBlobArrayForRead(
+ const std::vector<LargeBlobKey>& large_blob_keys,
+ LargeBlobReadCallback callback,
+ CtapDeviceResponseCode status,
+ base::Optional<LargeBlobArrayReader> large_blob_array_reader);
+
template <typename... Args>
void TaskClearProxy(base::OnceCallback<void(Args...)> callback, Args... args);
template <typename... Args>
@@ -195,6 +242,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceAuthenticator
CtapDeviceResponseCode status,
base::Optional<EnumerateCredentialsResponse> response);
+ size_t max_large_blob_fragment_length();
+
const std::unique_ptr<FidoDevice> device_;
base::Optional<AuthenticatorSupportedOptions> options_;
std::unique_ptr<FidoTask> task_;
diff --git a/chromium/device/fido/fido_device_authenticator_unittest.cc b/chromium/device/fido/fido_device_authenticator_unittest.cc
new file mode 100644
index 00000000000..5ba52e756f7
--- /dev/null
+++ b/chromium/device/fido/fido_device_authenticator_unittest.cc
@@ -0,0 +1,218 @@
+// 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/fido_device_authenticator.h"
+
+#include <memory>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/optional.h"
+#include "base/test/task_environment.h"
+#include "device/fido/fido_constants.h"
+#include "device/fido/fido_parsing_utils.h"
+#include "device/fido/large_blob.h"
+#include "device/fido/pin.h"
+#include "device/fido/test_callback_receiver.h"
+#include "device/fido/virtual_ctap2_device.h"
+#include "device/fido/virtual_fido_device.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace device {
+
+namespace {
+
+using WriteCallback =
+ device::test::ValueCallbackReceiver<CtapDeviceResponseCode>;
+using ReadCallback = device::test::StatusAndValueCallbackReceiver<
+ CtapDeviceResponseCode,
+ base::Optional<std::vector<std::pair<LargeBlobKey, std::vector<uint8_t>>>>>;
+using PinCallback = device::test::StatusAndValueCallbackReceiver<
+ CtapDeviceResponseCode,
+ base::Optional<pin::TokenResponse>>;
+
+constexpr LargeBlobKey kDummyKey1 = {{0x01}};
+constexpr LargeBlobKey kDummyKey2 = {{0x02}};
+constexpr std::array<uint8_t, 4> kSmallBlob1 = {'r', 'o', 's', 'a'};
+constexpr std::array<uint8_t, 4> kSmallBlob2 = {'l', 'u', 'm', 'a'};
+constexpr std::array<uint8_t, 4> kSmallBlob3 = {'s', 't', 'a', 'r'};
+constexpr size_t kMaxStorageSize = 4096;
+constexpr char kPin[] = "1234";
+
+class FidoDeviceAuthenticatorTest : public testing::Test {
+ public:
+ void SetUp() override {
+ VirtualCtap2Device::Config config;
+ config.pin_support = true;
+ config.large_blob_support = true;
+ config.resident_key_support = true;
+ config.available_large_blob_storage = kMaxStorageSize;
+ config.pin_uv_auth_token_support = true;
+ config.ctap2_versions = {Ctap2Version::kCtap2_1};
+
+ authenticator_state_ = base::MakeRefCounted<VirtualFidoDevice::State>();
+ auto virtual_device =
+ std::make_unique<VirtualCtap2Device>(authenticator_state_, config);
+ virtual_device_ = virtual_device.get();
+ authenticator_ =
+ std::make_unique<FidoDeviceAuthenticator>(std::move(virtual_device));
+
+ device::test::TestCallbackReceiver<> callback;
+ authenticator_->InitializeAuthenticator(callback.callback());
+ callback.WaitForCallback();
+ }
+
+ protected:
+ scoped_refptr<VirtualFidoDevice::State> authenticator_state_;
+ std::unique_ptr<FidoDeviceAuthenticator> authenticator_;
+ VirtualCtap2Device* virtual_device_;
+
+ private:
+ base::test::SingleThreadTaskEnvironment task_environment_;
+};
+
+TEST_F(FidoDeviceAuthenticatorTest, TestReadEmptyLargeBlob) {
+ ReadCallback callback;
+ authenticator_->ReadLargeBlob({kDummyKey1}, base::nullopt,
+ callback.callback());
+
+ callback.WaitForCallback();
+ EXPECT_EQ(CtapDeviceResponseCode::kSuccess, callback.status());
+ EXPECT_EQ(0u, callback.value()->size());
+}
+
+TEST_F(FidoDeviceAuthenticatorTest, TestReadInvalidLargeBlob) {
+ authenticator_state_->large_blob[0] += 1;
+ ReadCallback callback;
+ authenticator_->ReadLargeBlob({kDummyKey1}, base::nullopt,
+ callback.callback());
+
+ callback.WaitForCallback();
+ EXPECT_EQ(CtapDeviceResponseCode::kCtap2ErrIntegrityFailure,
+ callback.status());
+ EXPECT_FALSE(callback.value());
+}
+
+// Test reading and writing a blob that fits in a single fragment.
+TEST_F(FidoDeviceAuthenticatorTest, TestWriteSmallBlob) {
+ std::vector<uint8_t> small_blob =
+ fido_parsing_utils::Materialize(kSmallBlob1);
+ WriteCallback write_callback;
+ authenticator_->WriteLargeBlob(small_blob, {kDummyKey1}, base::nullopt,
+ write_callback.callback());
+
+ write_callback.WaitForCallback();
+ ASSERT_EQ(CtapDeviceResponseCode::kSuccess, write_callback.value());
+
+ ReadCallback read_callback;
+ authenticator_->ReadLargeBlob({kDummyKey1}, base::nullopt,
+ read_callback.callback());
+ read_callback.WaitForCallback();
+ ASSERT_EQ(CtapDeviceResponseCode::kSuccess, read_callback.status());
+ auto large_blob_array = read_callback.value();
+ ASSERT_TRUE(large_blob_array);
+ ASSERT_EQ(1u, large_blob_array->size());
+ EXPECT_EQ(kDummyKey1, large_blob_array->at(0).first);
+ EXPECT_EQ(small_blob, large_blob_array->at(0).second);
+}
+
+// Test reading and writing a blob that must fit in multiple fragments.
+TEST_F(FidoDeviceAuthenticatorTest, TestWriteLargeBlob) {
+ std::vector<uint8_t> large_blob;
+ large_blob.reserve(2048);
+ for (size_t i = 0; i < large_blob.capacity(); ++i) {
+ large_blob.emplace_back(i % 0xFF);
+ }
+
+ WriteCallback write_callback;
+ authenticator_->WriteLargeBlob(large_blob, {kDummyKey1}, base::nullopt,
+ write_callback.callback());
+
+ write_callback.WaitForCallback();
+ ASSERT_EQ(CtapDeviceResponseCode::kSuccess, write_callback.value());
+
+ ReadCallback read_callback;
+ authenticator_->ReadLargeBlob({kDummyKey1}, base::nullopt,
+ read_callback.callback());
+ read_callback.WaitForCallback();
+ ASSERT_EQ(CtapDeviceResponseCode::kSuccess, read_callback.status());
+ auto large_blob_array = read_callback.value();
+ ASSERT_TRUE(large_blob_array);
+ ASSERT_EQ(1u, large_blob_array->size());
+ EXPECT_EQ(kDummyKey1, large_blob_array->at(0).first);
+ EXPECT_EQ(large_blob, large_blob_array->at(0).second);
+}
+
+// Test reading and writing a blob using a PinUvAuthToken.
+TEST_F(FidoDeviceAuthenticatorTest, TestWriteSmallBlobWithToken) {
+ virtual_device_->SetPin(kPin);
+ PinCallback pin_callback;
+ authenticator_->GetPINToken(kPin, {pin::Permissions::kLargeBlobWrite},
+ /*rp_id=*/base::nullopt, pin_callback.callback());
+ pin_callback.WaitForCallback();
+ ASSERT_EQ(CtapDeviceResponseCode::kSuccess, pin_callback.status());
+ pin::TokenResponse pin_token = *pin_callback.value();
+
+ std::vector<uint8_t> small_blob =
+ fido_parsing_utils::Materialize(kSmallBlob1);
+ WriteCallback write_callback;
+ authenticator_->WriteLargeBlob(small_blob, {kDummyKey1}, pin_token,
+ write_callback.callback());
+ write_callback.WaitForCallback();
+ ASSERT_EQ(CtapDeviceResponseCode::kSuccess, write_callback.value());
+
+ ReadCallback read_callback;
+ authenticator_->ReadLargeBlob({kDummyKey1}, pin_token,
+ read_callback.callback());
+ read_callback.WaitForCallback();
+ ASSERT_EQ(CtapDeviceResponseCode::kSuccess, read_callback.status());
+ auto large_blob_array = read_callback.value();
+ ASSERT_TRUE(large_blob_array);
+ ASSERT_EQ(1u, large_blob_array->size());
+ EXPECT_EQ(kDummyKey1, large_blob_array->at(0).first);
+ EXPECT_EQ(small_blob, large_blob_array->at(0).second);
+}
+
+// Test updating a large blob in an array with multiple entries corresponding to
+// other keys.
+TEST_F(FidoDeviceAuthenticatorTest, TestUpdateLargeBlob) {
+ WriteCallback write_callback1;
+ authenticator_->WriteLargeBlob(fido_parsing_utils::Materialize(kSmallBlob1),
+ {kDummyKey1}, base::nullopt,
+ write_callback1.callback());
+ write_callback1.WaitForCallback();
+ ASSERT_EQ(CtapDeviceResponseCode::kSuccess, write_callback1.value());
+
+ WriteCallback write_callback2;
+ std::vector<uint8_t> small_blob2 =
+ fido_parsing_utils::Materialize(kSmallBlob2);
+ authenticator_->WriteLargeBlob(small_blob2, {kDummyKey2}, base::nullopt,
+ write_callback2.callback());
+ write_callback2.WaitForCallback();
+ ASSERT_EQ(CtapDeviceResponseCode::kSuccess, write_callback2.value());
+
+ // Update the first entry.
+ WriteCallback write_callback3;
+ std::vector<uint8_t> small_blob3 =
+ fido_parsing_utils::Materialize(kSmallBlob3);
+ authenticator_->WriteLargeBlob(small_blob3, {kDummyKey1}, base::nullopt,
+ write_callback3.callback());
+ write_callback3.WaitForCallback();
+ ASSERT_EQ(CtapDeviceResponseCode::kSuccess, write_callback3.value());
+
+ ReadCallback read_callback;
+ authenticator_->ReadLargeBlob({kDummyKey1, kDummyKey2}, base::nullopt,
+ read_callback.callback());
+ read_callback.WaitForCallback();
+ ASSERT_EQ(CtapDeviceResponseCode::kSuccess, read_callback.status());
+ auto large_blob_array = read_callback.value();
+ ASSERT_TRUE(large_blob_array);
+ EXPECT_THAT(*large_blob_array, testing::UnorderedElementsAre(
+ std::make_pair(kDummyKey1, small_blob3),
+ std::make_pair(kDummyKey2, small_blob2)));
+}
+
+} // namespace
+
+} // namespace device
diff --git a/chromium/device/fido/fido_device_discovery.cc b/chromium/device/fido/fido_device_discovery.cc
index a581c4ecb26..64eb23acc79 100644
--- a/chromium/device/fido/fido_device_discovery.cc
+++ b/chromium/device/fido/fido_device_discovery.cc
@@ -14,6 +14,7 @@
namespace device {
+FidoDeviceDiscovery::BLEObserver::~BLEObserver() = default;
FidoDeviceDiscovery::Observer::~Observer() = default;
FidoDeviceDiscovery::FidoDeviceDiscovery(FidoTransportProtocol transport)
diff --git a/chromium/device/fido/fido_device_discovery.h b/chromium/device/fido/fido_device_discovery.h
index f935f6fef12..6bf00bfab94 100644
--- a/chromium/device/fido/fido_device_discovery.h
+++ b/chromium/device/fido/fido_device_discovery.h
@@ -27,6 +27,15 @@ class FidoDeviceAuthenticator;
class COMPONENT_EXPORT(DEVICE_FIDO) FidoDeviceDiscovery
: public FidoDiscoveryBase {
public:
+ // BLEObserver is an interface for discoveries that watch for BLE adverts.
+ class BLEObserver {
+ public:
+ virtual ~BLEObserver();
+
+ virtual void OnBLEAdvertSeen(const std::string& address,
+ const std::array<uint8_t, 16>& eid) = 0;
+ };
+
enum class State {
kIdle,
kStarting,
diff --git a/chromium/device/fido/fido_discovery_factory.cc b/chromium/device/fido/fido_discovery_factory.cc
index 6fec78fb0e2..35ea31f118e 100644
--- a/chromium/device/fido/fido_discovery_factory.cc
+++ b/chromium/device/fido/fido_discovery_factory.cc
@@ -8,6 +8,7 @@
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/fido/aoa/android_accessory_discovery.h"
#include "device/fido/cable/fido_cable_discovery.h"
+#include "device/fido/cable/v2_discovery.h"
#include "device/fido/features.h"
#include "device/fido/fido_discovery_base.h"
@@ -35,39 +36,60 @@ namespace device {
FidoDiscoveryFactory::FidoDiscoveryFactory() = default;
FidoDiscoveryFactory::~FidoDiscoveryFactory() = default;
-std::unique_ptr<FidoDiscoveryBase> FidoDiscoveryFactory::Create(
+std::vector<std::unique_ptr<FidoDiscoveryBase>> FidoDiscoveryFactory::Create(
FidoTransportProtocol transport) {
switch (transport) {
case FidoTransportProtocol::kUsbHumanInterfaceDevice:
- return std::make_unique<FidoHidDiscovery>(hid_ignore_list_);
+ return SingleDiscovery(
+ std::make_unique<FidoHidDiscovery>(hid_ignore_list_));
case FidoTransportProtocol::kBluetoothLowEnergy:
- return nullptr;
+ return {};
case FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy:
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_, network_context_);
+ std::unique_ptr<cablev2::Discovery> v2_discovery;
+ if (qr_generator_key_.has_value()) {
+ v2_discovery = std::make_unique<cablev2::Discovery>(
+ network_context_, *qr_generator_key_, std::move(v2_pairings_),
+ std::move(cable_pairing_callback_));
+ }
+ std::unique_ptr<FidoDiscoveryBase> v1_discovery =
+ std::make_unique<FidoCableDiscovery>(
+ cable_data_.value_or(std::vector<CableDiscoveryData>()),
+ v2_discovery ? v2_discovery.get() : nullptr);
+
+ std::vector<std::unique_ptr<FidoDiscoveryBase>> ret;
+ if (v2_discovery) {
+ ret.emplace_back(std::move(v2_discovery));
+ }
+ ret.emplace_back(std::move(v1_discovery));
+ return ret;
}
- return nullptr;
+ return {};
case FidoTransportProtocol::kNearFieldCommunication:
// TODO(https://crbug.com/825949): Add NFC support.
- return nullptr;
- case FidoTransportProtocol::kInternal:
+ return {};
+ case FidoTransportProtocol::kInternal: {
#if defined(OS_MAC) || defined(OS_CHROMEOS)
- return MaybeCreatePlatformDiscovery();
+ std::unique_ptr<FidoDiscoveryBase> discovery =
+ MaybeCreatePlatformDiscovery();
+ if (discovery) {
+ return SingleDiscovery(std::move(discovery));
+ }
+ return {};
#else
- return nullptr;
+ return {};
#endif
+ }
case FidoTransportProtocol::kAndroidAccessory:
if (usb_device_manager_) {
- return std::make_unique<AndroidAccessoryDiscovery>(
- std::move(usb_device_manager_.value()));
+ return SingleDiscovery(std::make_unique<AndroidAccessoryDiscovery>(
+ std::move(usb_device_manager_.value())));
}
- return nullptr;
+ return {};
}
NOTREACHED() << "Unhandled transport type";
- return nullptr;
+ return {};
}
bool FidoDiscoveryFactory::IsTestOverride() {
@@ -76,9 +98,12 @@ bool FidoDiscoveryFactory::IsTestOverride() {
void FidoDiscoveryFactory::set_cable_data(
std::vector<CableDiscoveryData> cable_data,
- base::Optional<QRGeneratorKey> qr_generator_key) {
+ const base::Optional<std::array<uint8_t, cablev2::kQRKeySize>>&
+ qr_generator_key,
+ std::vector<std::unique_ptr<cablev2::Pairing>> v2_pairings) {
cable_data_ = std::move(cable_data);
qr_generator_key_ = std::move(qr_generator_key);
+ v2_pairings_ = std::move(v2_pairings);
}
void FidoDiscoveryFactory::set_usb_device_manager(
@@ -92,7 +117,7 @@ void FidoDiscoveryFactory::set_network_context(
}
void FidoDiscoveryFactory::set_cable_pairing_callback(
- base::RepeatingCallback<void(std::unique_ptr<CableDiscoveryData>)>
+ base::RepeatingCallback<void(std::unique_ptr<cablev2::Pairing>)>
pairing_callback) {
cable_pairing_callback_.emplace(std::move(pairing_callback));
}
@@ -102,6 +127,19 @@ void FidoDiscoveryFactory::set_hid_ignore_list(
hid_ignore_list_ = std::move(hid_ignore_list);
}
+// static
+std::vector<std::unique_ptr<FidoDiscoveryBase>>
+FidoDiscoveryFactory::SingleDiscovery(
+ std::unique_ptr<FidoDiscoveryBase> discovery) {
+ if (!discovery) {
+ return {};
+ }
+
+ std::vector<std::unique_ptr<FidoDiscoveryBase>> ret;
+ ret.emplace_back(std::move(discovery));
+ return ret;
+}
+
#if defined(OS_WIN)
void FidoDiscoveryFactory::set_win_webauthn_api(WinWebAuthnApi* api) {
win_webauthn_api_ = api;
diff --git a/chromium/device/fido/fido_discovery_factory.h b/chromium/device/fido/fido_discovery_factory.h
index 9c6de139323..a8494d7d239 100644
--- a/chromium/device/fido/fido_discovery_factory.h
+++ b/chromium/device/fido/fido_discovery_factory.h
@@ -12,6 +12,7 @@
#include "base/optional.h"
#include "build/build_config.h"
#include "device/fido/cable/cable_discovery_data.h"
+#include "device/fido/cable/v2_constants.h"
#include "device/fido/fido_device_discovery.h"
#include "device/fido/fido_discovery_base.h"
#include "device/fido/fido_request_handler_base.h"
@@ -38,10 +39,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDiscoveryFactory {
FidoDiscoveryFactory();
virtual ~FidoDiscoveryFactory();
- // Instantiates a FidoDiscoveryBase for the given transport.
+ // Instantiates one or more FidoDiscoveryBases for the given transport.
//
// FidoTransportProtocol::kUsbHumanInterfaceDevice is not valid on Android.
- virtual std::unique_ptr<FidoDiscoveryBase> Create(
+ virtual std::vector<std::unique_ptr<FidoDiscoveryBase>> Create(
FidoTransportProtocol transport);
// Returns whether the current instance is an override injected by the
@@ -49,8 +50,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDiscoveryFactory {
virtual bool IsTestOverride();
// set_cable_data configures caBLE obtained via a WebAuthn extension.
- void set_cable_data(std::vector<CableDiscoveryData> cable_data,
- base::Optional<QRGeneratorKey> qr_generator_key);
+ void set_cable_data(
+ std::vector<CableDiscoveryData> cable_data,
+ const base::Optional<std::array<uint8_t, cablev2::kQRKeySize>>&
+ qr_generator_key,
+ std::vector<std::unique_ptr<cablev2::Pairing>> v2_pairings);
void set_usb_device_manager(mojo::Remote<device::mojom::UsbDeviceManager>);
@@ -60,7 +64,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDiscoveryFactory {
// called when a QR handshake results in a phone wishing to pair with this
// browser.
void set_cable_pairing_callback(
- base::RepeatingCallback<void(std::unique_ptr<CableDiscoveryData>)>);
+ base::RepeatingCallback<void(std::unique_ptr<cablev2::Pairing>)>);
void set_hid_ignore_list(base::flat_set<VidPid> hid_ignore_list);
@@ -84,6 +88,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDiscoveryFactory {
WinWebAuthnApi* win_webauthn_api() const;
#endif // defined(OS_WIN)
+ protected:
+ static std::vector<std::unique_ptr<FidoDiscoveryBase>> SingleDiscovery(
+ std::unique_ptr<FidoDiscoveryBase> discovery);
+
private:
#if defined(OS_MAC) || defined(OS_CHROMEOS)
std::unique_ptr<FidoDiscoveryBase> MaybeCreatePlatformDiscovery() const;
@@ -96,9 +104,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoDiscoveryFactory {
usb_device_manager_;
network::mojom::NetworkContext* network_context_ = nullptr;
base::Optional<std::vector<CableDiscoveryData>> cable_data_;
- base::Optional<QRGeneratorKey> qr_generator_key_;
+ base::Optional<std::array<uint8_t, cablev2::kQRKeySize>> qr_generator_key_;
+ std::vector<std::unique_ptr<cablev2::Pairing>> v2_pairings_;
base::Optional<
- base::RepeatingCallback<void(std::unique_ptr<CableDiscoveryData>)>>
+ base::RepeatingCallback<void(std::unique_ptr<cablev2::Pairing>)>>
cable_pairing_callback_;
#if defined(OS_WIN)
WinWebAuthnApi* win_webauthn_api_ = nullptr;
diff --git a/chromium/device/fido/fido_parsing_utils.h b/chromium/device/fido/fido_parsing_utils.h
index 431361483e3..6304cf9f841 100644
--- a/chromium/device/fido/fido_parsing_utils.h
+++ b/chromium/device/fido/fido_parsing_utils.h
@@ -139,6 +139,17 @@ bool CopyCBORBytestring(std::array<uint8_t, N>* out,
return ExtractArray(bytestring, /*pos=*/0, out);
}
+constexpr std::array<uint8_t, 4> Uint32LittleEndian(uint32_t value) {
+ return {value & 0xFF, value >> 8 & 0xFF, value >> 16 & 0xFF,
+ value >> 24 & 0xFF};
+}
+
+constexpr std::array<uint8_t, 8> Uint64LittleEndian(uint64_t value) {
+ return {value & 0xFF, value >> 8 & 0xFF, value >> 16 & 0xFF,
+ value >> 24 & 0xFF, value >> 32 & 0xFF, value >> 40 & 0xFF,
+ value >> 48 & 0xFF, value >> 56 & 0xFF};
+}
+
} // namespace fido_parsing_utils
} // namespace device
diff --git a/chromium/device/fido/fido_request_handler_base.cc b/chromium/device/fido/fido_request_handler_base.cc
index a7f687605d5..7eb2d155745 100644
--- a/chromium/device/fido/fido_request_handler_base.cc
+++ b/chromium/device/fido/fido_request_handler_base.cc
@@ -62,9 +62,9 @@ void FidoRequestHandlerBase::InitDiscoveries(
const base::flat_set<FidoTransportProtocol>& available_transports) {
transport_availability_info_.available_transports = available_transports;
for (const auto transport : available_transports) {
- std::unique_ptr<FidoDiscoveryBase> discovery =
+ std::vector<std::unique_ptr<FidoDiscoveryBase>> discoveries =
fido_discovery_factory->Create(transport);
- if (discovery == nullptr) {
+ if (discoveries.empty()) {
// This can occur in tests when a ScopedVirtualU2fDevice is in effect and
// HID transports are not configured or when caBLE discovery data isn't
// available.
@@ -72,8 +72,10 @@ void FidoRequestHandlerBase::InitDiscoveries(
continue;
}
- discovery->set_observer(this);
- discoveries_.push_back(std::move(discovery));
+ for (auto& discovery : discoveries) {
+ discovery->set_observer(this);
+ discoveries_.emplace_back(std::move(discovery));
+ }
}
// Check if the platform supports BLE before trying to get a power manager.
diff --git a/chromium/device/fido/fido_types.h b/chromium/device/fido/fido_types.h
index c620387ddc3..c896e809247 100644
--- a/chromium/device/fido/fido_types.h
+++ b/chromium/device/fido/fido_types.h
@@ -5,18 +5,25 @@
#ifndef DEVICE_FIDO_FIDO_TYPES_H_
#define DEVICE_FIDO_FIDO_TYPES_H_
-// The definitions below are for mojo-mappable types that need to be
-// transferred from Blink. Types that have mojo equivalents are better placed
-// in fido_constants.h.
+// The definitions below are for mojo-mappable types that need to be transferred
+// from Blink. Types that do not have mojo equivalents are better placed in
+// fido_constants.h.
namespace device {
+// ProtocolVersion is the major protocol version of an authenticator device.
enum class ProtocolVersion {
kCtap2,
kU2f,
kUnknown,
};
+// Ctap2Version distinguishes different minor versions of the CTAP2 protocol.
+enum class Ctap2Version {
+ kCtap2_0,
+ kCtap2_1,
+};
+
enum class CredentialType { kPublicKey };
// Authenticator attachment constraint passed on from the relying party as a
@@ -29,6 +36,16 @@ enum class AuthenticatorAttachment {
kCrossPlatform,
};
+// A constraint on whether a client-side discoverable (resident) credential
+// should be created during registration.
+//
+// https://w3c.github.io/webauthn/#enum-residentKeyRequirement
+enum class ResidentKeyRequirement {
+ kDiscouraged,
+ kPreferred,
+ kRequired,
+};
+
// User verification constraint passed on from the relying party as a parameter
// for AuthenticatorSelectionCriteria and for CtapGetAssertion request.
// https://w3c.github.io/webauthn/#enumdef-userverificationrequirement
diff --git a/chromium/device/fido/get_assertion_request_handler.cc b/chromium/device/fido/get_assertion_request_handler.cc
index 51a0175422e..7adcf5b6cf8 100644
--- a/chromium/device/fido/get_assertion_request_handler.cc
+++ b/chromium/device/fido/get_assertion_request_handler.cc
@@ -337,11 +337,8 @@ void GetAssertionRequestHandler::DispatchRequest(
CtapGetAssertionRequest request(request_);
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,
+ authenticator->GetUvRetries(
+ base::BindOnce(&GetAssertionRequestHandler::OnStartUvTokenOrFallback,
weak_factory_.GetWeakPtr(), authenticator));
return;
}
@@ -703,6 +700,40 @@ void GetAssertionRequestHandler::OnHavePINToken(
DispatchRequestWithToken(std::move(*response));
}
+void GetAssertionRequestHandler::OnStartUvTokenOrFallback(
+ FidoAuthenticator* authenticator,
+ CtapDeviceResponseCode status,
+ base::Optional<pin::RetriesResponse> response) {
+ size_t retries;
+ if (status != CtapDeviceResponseCode::kSuccess) {
+ FIDO_LOG(ERROR) << "OnStartUvTokenOrFallback() failed for "
+ << authenticator_->GetDisplayName()
+ << ", assuming authenticator locked.";
+ retries = 0;
+ } else {
+ retries = response->retries;
+ }
+
+ if (retries == 0) {
+ if (authenticator->WillNeedPINToGetAssertion(request_, observer()) ==
+ PINDisposition::kUsePINForFallback) {
+ authenticator->GetTouch(base::BindOnce(
+ &GetAssertionRequestHandler::StartPINFallbackForInternalUv,
+ weak_factory_.GetWeakPtr(), authenticator));
+ return;
+ }
+ authenticator->GetTouch(base::BindOnce(
+ &GetAssertionRequestHandler::TerminateUnsatisfiableRequestPostTouch,
+ weak_factory_.GetWeakPtr(), authenticator));
+ }
+
+ base::Optional<std::string> rp_id(request_.rp_id);
+ authenticator->GetUvToken(
+ std::move(rp_id),
+ base::BindOnce(&GetAssertionRequestHandler::OnHaveUvToken,
+ weak_factory_.GetWeakPtr(), authenticator));
+}
+
void GetAssertionRequestHandler::OnUvRetriesResponse(
CtapDeviceResponseCode status,
base::Optional<pin::RetriesResponse> response) {
@@ -746,29 +777,19 @@ void GetAssertionRequestHandler::OnHaveUvToken(
return;
}
- if (status == CtapDeviceResponseCode::kCtap2ErrPinInvalid ||
+ if (status == CtapDeviceResponseCode::kCtap2ErrUvInvalid ||
status == CtapDeviceResponseCode::kCtap2ErrOperationDenied ||
status == CtapDeviceResponseCode::kCtap2ErrUvBlocked) {
if (status == CtapDeviceResponseCode::kCtap2ErrUvBlocked) {
- // This error is returned immediately without user interaction. Ask for a
- // touch and fall back to PIN or terminate the request if the device does
- // not support PIN.
- FIDO_LOG(DEBUG) << "Internal UV blocked for "
- << authenticator->GetDisplayName()
- << ", falling back to PIN.";
if (authenticator->WillNeedPINToGetAssertion(request_, observer()) ==
PINDisposition::kUsePINForFallback) {
- authenticator->GetTouch(base::BindOnce(
- &GetAssertionRequestHandler::StartPINFallbackForInternalUv,
- weak_factory_.GetWeakPtr(), authenticator));
+ StartPINFallbackForInternalUv(authenticator);
return;
}
- authenticator->GetTouch(base::BindOnce(
- &GetAssertionRequestHandler::TerminateUnsatisfiableRequestPostTouch,
- weak_factory_.GetWeakPtr(), authenticator));
+ TerminateUnsatisfiableRequestPostTouch(authenticator);
return;
}
- DCHECK(status == CtapDeviceResponseCode::kCtap2ErrPinInvalid ||
+ DCHECK(status == CtapDeviceResponseCode::kCtap2ErrUvInvalid ||
status == CtapDeviceResponseCode::kCtap2ErrOperationDenied);
CancelActiveAuthenticators(authenticator->GetId());
authenticator_ = authenticator;
diff --git a/chromium/device/fido/get_assertion_request_handler.h b/chromium/device/fido/get_assertion_request_handler.h
index 6933ebacb2c..06535a2d385 100644
--- a/chromium/device/fido/get_assertion_request_handler.h
+++ b/chromium/device/fido/get_assertion_request_handler.h
@@ -106,6 +106,9 @@ class COMPONENT_EXPORT(DEVICE_FIDO) GetAssertionRequestHandler
void OnHavePIN(std::string pin);
void OnHavePINToken(CtapDeviceResponseCode status,
base::Optional<pin::TokenResponse> response);
+ void OnStartUvTokenOrFallback(FidoAuthenticator* authenticator,
+ CtapDeviceResponseCode status,
+ base::Optional<pin::RetriesResponse> response);
void OnUvRetriesResponse(CtapDeviceResponseCode status,
base::Optional<pin::RetriesResponse> response);
void OnHaveUvToken(FidoAuthenticator* authenticator,
diff --git a/chromium/device/fido/hid/fake_hid_impl_for_testing.h b/chromium/device/fido/hid/fake_hid_impl_for_testing.h
index 15e87abae1b..894efab9b7a 100644
--- a/chromium/device/fido/hid/fake_hid_impl_for_testing.h
+++ b/chromium/device/fido/hid/fake_hid_impl_for_testing.h
@@ -111,7 +111,8 @@ class FakeFidoHidManager : public device::mojom::HidManager {
mojo::PendingRemote<mojom::HidConnectionClient> connection_client,
mojo::PendingRemote<mojom::HidConnectionWatcher> watcher,
ConnectCallback callback) override;
- void AddReceiver(mojo::PendingReceiver<device::mojom::HidManager> receiver);
+ void AddReceiver(
+ mojo::PendingReceiver<device::mojom::HidManager> receiver) override;
void AddDevice(device::mojom::HidDeviceInfoPtr device);
void AddDeviceAndSetConnection(
device::mojom::HidDeviceInfoPtr device,
diff --git a/chromium/device/fido/large_blob.cc b/chromium/device/fido/large_blob.cc
new file mode 100644
index 00000000000..b69b4b72ee9
--- /dev/null
+++ b/chromium/device/fido/large_blob.cc
@@ -0,0 +1,299 @@
+// 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/large_blob.h"
+#include "base/containers/span.h"
+#include "components/cbor/reader.h"
+#include "components/cbor/writer.h"
+#include "crypto/aead.h"
+#include "crypto/random.h"
+#include "crypto/sha2.h"
+#include "device/fido/fido_parsing_utils.h"
+#include "device/fido/pin.h"
+
+namespace device {
+
+namespace {
+// The number of bytes the large blob validation hash is truncated to.
+constexpr size_t kTruncatedHashBytes = 16;
+constexpr std::array<uint8_t, 4> kLargeBlobADPrefix = {'b', 'l', 'o', 'b'};
+constexpr size_t kAssociatedDataLength = kLargeBlobADPrefix.size() + 8;
+
+std::array<uint8_t, kAssociatedDataLength> GenerateLargeBlobAdditionalData(
+ size_t size) {
+ std::array<uint8_t, kAssociatedDataLength> additional_data;
+ const std::array<uint8_t, 8>& size_array =
+ fido_parsing_utils::Uint64LittleEndian(size);
+ std::copy(kLargeBlobADPrefix.begin(), kLargeBlobADPrefix.end(),
+ additional_data.begin());
+ std::copy(size_array.begin(), size_array.end(),
+ additional_data.begin() + kLargeBlobADPrefix.size());
+ return additional_data;
+}
+
+} // namespace
+
+LargeBlobArrayFragment::LargeBlobArrayFragment(const std::vector<uint8_t> bytes,
+ const size_t offset)
+ : bytes(std::move(bytes)), offset(offset) {}
+LargeBlobArrayFragment::~LargeBlobArrayFragment() = default;
+LargeBlobArrayFragment::LargeBlobArrayFragment(LargeBlobArrayFragment&&) =
+ default;
+
+bool VerifyLargeBlobArrayIntegrity(base::span<const uint8_t> large_blob_array) {
+ if (large_blob_array.size() <= kTruncatedHashBytes) {
+ return false;
+ }
+ const size_t trail_offset = large_blob_array.size() - kTruncatedHashBytes;
+ std::array<uint8_t, crypto::kSHA256Length> large_blob_hash =
+ crypto::SHA256Hash(large_blob_array.subspan(0, trail_offset));
+
+ base::span<const uint8_t> large_blob_trail =
+ large_blob_array.subspan(trail_offset);
+ return std::equal(large_blob_hash.begin(),
+ large_blob_hash.begin() + kTruncatedHashBytes,
+ large_blob_trail.begin(), large_blob_trail.end());
+}
+
+// static
+LargeBlobsRequest LargeBlobsRequest::ForRead(size_t bytes, size_t offset) {
+ DCHECK_GT(bytes, 0u);
+ LargeBlobsRequest request;
+ request.get_ = bytes;
+ request.offset_ = offset;
+ return request;
+}
+
+// static
+LargeBlobsRequest LargeBlobsRequest::ForWrite(LargeBlobArrayFragment fragment,
+ size_t length) {
+ LargeBlobsRequest request;
+ if (fragment.offset == 0) {
+ request.length_ = length;
+ }
+ request.offset_ = fragment.offset;
+ request.set_ = std::move(fragment.bytes);
+ return request;
+}
+
+LargeBlobsRequest::LargeBlobsRequest() = default;
+LargeBlobsRequest::LargeBlobsRequest(LargeBlobsRequest&& other) = default;
+LargeBlobsRequest::~LargeBlobsRequest() = default;
+
+void LargeBlobsRequest::SetPinParam(
+ const pin::TokenResponse& pin_uv_auth_token) {
+ pin_uv_auth_protocol_ = pin::kProtocolVersion;
+ std::vector<uint8_t> pin_auth(pin::kPinUvAuthTokenSafetyPadding.begin(),
+ pin::kPinUvAuthTokenSafetyPadding.end());
+ pin_auth.insert(pin_auth.end(), kLargeBlobPinPrefix.begin(),
+ kLargeBlobPinPrefix.end());
+ const std::array<uint8_t, 4> offset_array =
+ fido_parsing_utils::Uint32LittleEndian(offset_);
+ pin_auth.insert(pin_auth.end(), offset_array.begin(), offset_array.end());
+ if (set_) {
+ pin_auth.insert(pin_auth.end(), set_->begin(), set_->end());
+ }
+ pin_uv_auth_param_ = pin_uv_auth_token.PinAuth(pin_auth);
+}
+
+// static
+base::Optional<LargeBlobsResponse> LargeBlobsResponse::ParseForRead(
+ const size_t bytes_to_read,
+ const base::Optional<cbor::Value>& cbor_response) {
+ if (!cbor_response || !cbor_response->is_map()) {
+ return base::nullopt;
+ }
+
+ const cbor::Value::MapValue& map = cbor_response->GetMap();
+ auto it =
+ map.find(cbor::Value(static_cast<int>(LargeBlobsResponseKey::kConfig)));
+ if (it == map.end() || !it->second.is_bytestring()) {
+ return base::nullopt;
+ }
+
+ const std::vector<uint8_t>& config = it->second.GetBytestring();
+ if (config.size() > bytes_to_read) {
+ return base::nullopt;
+ }
+
+ return LargeBlobsResponse(std::move(config));
+}
+
+// static
+base::Optional<LargeBlobsResponse> LargeBlobsResponse::ParseForWrite(
+ const base::Optional<cbor::Value>& cbor_response) {
+ // For writing, we expect an empty response.
+ if (cbor_response) {
+ return base::nullopt;
+ }
+
+ return LargeBlobsResponse();
+}
+
+LargeBlobsResponse::LargeBlobsResponse(
+ base::Optional<std::vector<uint8_t>> config)
+ : config_(std::move(config)) {}
+LargeBlobsResponse::LargeBlobsResponse(LargeBlobsResponse&& other) = default;
+LargeBlobsResponse& LargeBlobsResponse::operator=(LargeBlobsResponse&& other) =
+ default;
+LargeBlobsResponse::~LargeBlobsResponse() = default;
+
+std::pair<CtapRequestCommand, base::Optional<cbor::Value>>
+AsCTAPRequestValuePair(const LargeBlobsRequest& request) {
+ cbor::Value::MapValue map;
+ if (request.get_) {
+ map.emplace(static_cast<int>(LargeBlobsRequestKey::kGet), *request.get_);
+ }
+ if (request.set_) {
+ map.emplace(static_cast<int>(LargeBlobsRequestKey::kSet), *request.set_);
+ }
+ map.emplace(static_cast<int>(LargeBlobsRequestKey::kOffset), request.offset_);
+ if (request.length_) {
+ map.emplace(static_cast<int>(LargeBlobsRequestKey::kLength),
+ *request.length_);
+ }
+ if (request.pin_uv_auth_param_) {
+ map.emplace(static_cast<int>(LargeBlobsRequestKey::kPinUvAuthParam),
+ *request.pin_uv_auth_param_);
+ }
+ if (request.pin_uv_auth_protocol_) {
+ map.emplace(static_cast<int>(LargeBlobsRequestKey::kPinUvAuthProtocol),
+ *request.pin_uv_auth_protocol_);
+ }
+ return std::make_pair(CtapRequestCommand::kAuthenticatorLargeBlobs,
+ cbor::Value(std::move(map)));
+}
+
+// static.
+base::Optional<LargeBlobData> LargeBlobData::Parse(const cbor::Value& value) {
+ if (!value.is_map()) {
+ return base::nullopt;
+ }
+ const cbor::Value::MapValue& map = value.GetMap();
+ auto ciphertext_it =
+ map.find(cbor::Value(static_cast<int>(LargeBlobDataKeys::kCiphertext)));
+ if (ciphertext_it == map.end() || !ciphertext_it->second.is_bytestring()) {
+ return base::nullopt;
+ }
+ auto nonce_it =
+ map.find(cbor::Value(static_cast<int>(LargeBlobDataKeys::kNonce)));
+ if (nonce_it == map.end() || !nonce_it->second.is_bytestring() ||
+ nonce_it->second.GetBytestring().size() != kLargeBlobArrayNonceLength) {
+ return base::nullopt;
+ }
+ auto orig_size_it =
+ map.find(cbor::Value(static_cast<int>(LargeBlobDataKeys::kOrigSize)));
+ if (orig_size_it == map.end() || !orig_size_it->second.is_unsigned()) {
+ return base::nullopt;
+ }
+ return LargeBlobData(ciphertext_it->second.GetBytestring(),
+ base::make_span<kLargeBlobArrayNonceLength>(
+ nonce_it->second.GetBytestring()),
+ orig_size_it->second.GetUnsigned());
+}
+
+LargeBlobData::LargeBlobData(
+ std::vector<uint8_t> ciphertext,
+ base::span<const uint8_t, kLargeBlobArrayNonceLength> nonce,
+ int64_t orig_size)
+ : ciphertext_(std::move(ciphertext)), orig_size_(std::move(orig_size)) {
+ std::copy(nonce.begin(), nonce.end(), nonce_.begin());
+}
+LargeBlobData::LargeBlobData(LargeBlobKey key, std::vector<uint8_t> blob) {
+ orig_size_ = blob.size();
+ crypto::Aead aead(crypto::Aead::AeadAlgorithm::AES_256_GCM);
+ aead.Init(key);
+ crypto::RandBytes(nonce_);
+ ciphertext_ =
+ aead.Seal(blob, nonce_, GenerateLargeBlobAdditionalData(orig_size_));
+}
+LargeBlobData::LargeBlobData(LargeBlobData&&) = default;
+LargeBlobData& LargeBlobData::operator=(LargeBlobData&&) = default;
+LargeBlobData::~LargeBlobData() = default;
+
+bool LargeBlobData::operator==(const LargeBlobData& other) const {
+ return ciphertext_ == other.ciphertext_ && nonce_ == other.nonce_ &&
+ orig_size_ == other.orig_size_;
+}
+
+base::Optional<std::vector<uint8_t>> LargeBlobData::Decrypt(
+ LargeBlobKey key) const {
+ crypto::Aead aead(crypto::Aead::AeadAlgorithm::AES_256_GCM);
+ aead.Init(key);
+ return aead.Open(ciphertext_, nonce_,
+ GenerateLargeBlobAdditionalData(orig_size_));
+}
+
+cbor::Value::MapValue LargeBlobData::AsCBOR() const {
+ cbor::Value::MapValue map;
+ map.emplace(static_cast<int>(LargeBlobDataKeys::kCiphertext), ciphertext_);
+ map.emplace(static_cast<int>(LargeBlobDataKeys::kNonce), nonce_);
+ map.emplace(static_cast<int>(LargeBlobDataKeys::kOrigSize), orig_size_);
+ return map;
+}
+
+LargeBlobArrayReader::LargeBlobArrayReader() = default;
+LargeBlobArrayReader::LargeBlobArrayReader(LargeBlobArrayReader&&) = default;
+LargeBlobArrayReader::~LargeBlobArrayReader() = default;
+
+void LargeBlobArrayReader::Append(const std::vector<uint8_t>& fragment) {
+ bytes_.insert(bytes_.end(), fragment.begin(), fragment.end());
+}
+
+base::Optional<std::vector<LargeBlobData>> LargeBlobArrayReader::Materialize() {
+ if (!VerifyLargeBlobArrayIntegrity(bytes_)) {
+ return base::nullopt;
+ }
+
+ base::span<const uint8_t> cbor_bytes =
+ base::make_span(bytes_.data(), bytes_.size() - kTruncatedHashBytes);
+ base::Optional<cbor::Value> cbor = cbor::Reader::Read(cbor_bytes);
+ if (!cbor || !cbor->is_array()) {
+ return base::nullopt;
+ }
+
+ std::vector<LargeBlobData> large_blob_array;
+ const cbor::Value::ArrayValue& array = cbor->GetArray();
+ for (const cbor::Value& value : array) {
+ base::Optional<LargeBlobData> large_blob_data = LargeBlobData::Parse(value);
+ if (!large_blob_data) {
+ continue;
+ }
+
+ large_blob_array.emplace_back(std::move(*large_blob_data));
+ }
+
+ return large_blob_array;
+}
+
+LargeBlobArrayWriter::LargeBlobArrayWriter(
+ const std::vector<LargeBlobData>& large_blob_array) {
+ cbor::Value::ArrayValue array;
+ for (const LargeBlobData& large_blob_data : large_blob_array) {
+ array.emplace_back(large_blob_data.AsCBOR());
+ }
+ bytes_ = *cbor::Writer::Write(cbor::Value(array));
+
+ std::array<uint8_t, crypto::kSHA256Length> large_blob_hash =
+ crypto::SHA256Hash(bytes_);
+ bytes_.insert(bytes_.end(), large_blob_hash.begin(),
+ large_blob_hash.begin() + kTruncatedHashBytes);
+ DCHECK(VerifyLargeBlobArrayIntegrity(bytes_));
+}
+LargeBlobArrayWriter::LargeBlobArrayWriter(LargeBlobArrayWriter&&) = default;
+LargeBlobArrayWriter::~LargeBlobArrayWriter() = default;
+
+LargeBlobArrayFragment LargeBlobArrayWriter::Pop(size_t length) {
+ CHECK(has_remaining_fragments());
+ length = std::min(length, bytes_.size() - offset_);
+
+ LargeBlobArrayFragment fragment{
+ fido_parsing_utils::Materialize(
+ base::make_span(bytes_.data() + offset_, length)),
+ offset_};
+ offset_ += length;
+ return fragment;
+}
+
+} // namespace device
diff --git a/chromium/device/fido/large_blob.h b/chromium/device/fido/large_blob.h
new file mode 100644
index 00000000000..dba7ef0298a
--- /dev/null
+++ b/chromium/device/fido/large_blob.h
@@ -0,0 +1,203 @@
+// 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_LARGE_BLOB_H_
+#define DEVICE_FIDO_LARGE_BLOB_H_
+
+#include <cstdint>
+#include <cstdlib>
+#include <vector>
+
+#include "base/component_export.h"
+#include "device/fido/fido_constants.h"
+#include "device/fido/pin.h"
+
+namespace device {
+
+// https://drafts.fidoalliance.org/fido-2/stable-links-to-latest/fido-client-to-authenticator-protocol.html#largeBlobsRW
+enum class LargeBlobsRequestKey : uint8_t {
+ kGet = 0x01,
+ kSet = 0x02,
+ kOffset = 0x03,
+ kLength = 0x04,
+ kPinUvAuthParam = 0x05,
+ kPinUvAuthProtocol = 0x06,
+};
+
+// https://drafts.fidoalliance.org/fido-2/stable-links-to-latest/fido-client-to-authenticator-protocol.html#largeBlobsRW
+enum class LargeBlobsResponseKey : uint8_t {
+ kConfig = 0x01,
+};
+
+// https://drafts.fidoalliance.org/fido-2/stable-links-to-latest/fido-client-to-authenticator-protocol.html#large-blob
+enum class LargeBlobDataKeys : uint8_t {
+ kCiphertext = 0x01,
+ kNonce = 0x02,
+ kOrigSize = 0x03,
+};
+
+enum class LargeBlobOperation {
+ kNone,
+ kRead,
+ kWrite,
+};
+
+using LargeBlobKey = std::array<uint8_t, kLargeBlobKeyLength>;
+
+constexpr size_t kLargeBlobDefaultMaxFragmentLength = 960;
+constexpr size_t kLargeBlobReadEncodingOverhead = 64;
+constexpr size_t kLargeBlobArrayNonceLength = 12;
+constexpr std::array<uint8_t, 2> kLargeBlobPinPrefix = {0x0c, 0x00};
+
+struct COMPONENT_EXPORT(DEVICE_FIDO) LargeBlobArrayFragment {
+ LargeBlobArrayFragment(std::vector<uint8_t> bytes, size_t offset);
+ ~LargeBlobArrayFragment();
+ LargeBlobArrayFragment(const LargeBlobArrayFragment&) = delete;
+ LargeBlobArrayFragment operator=(const LargeBlobArrayFragment&) = delete;
+ LargeBlobArrayFragment(LargeBlobArrayFragment&&);
+ const std::vector<uint8_t> bytes;
+ const size_t offset;
+};
+
+COMPONENT_EXPORT(DEVICE_FIDO)
+bool VerifyLargeBlobArrayIntegrity(base::span<const uint8_t> large_blob_array);
+
+class LargeBlobsRequest {
+ public:
+ ~LargeBlobsRequest();
+ LargeBlobsRequest(const LargeBlobsRequest&) = delete;
+ LargeBlobsRequest operator=(const LargeBlobsRequest&) = delete;
+ LargeBlobsRequest(LargeBlobsRequest&& other);
+
+ static LargeBlobsRequest ForRead(size_t bytes, size_t offset);
+ static LargeBlobsRequest ForWrite(LargeBlobArrayFragment fragment,
+ size_t length);
+
+ void SetPinParam(const pin::TokenResponse& pin_uv_auth_token);
+
+ friend std::pair<CtapRequestCommand, base::Optional<cbor::Value>>
+ AsCTAPRequestValuePair(const LargeBlobsRequest& request);
+
+ private:
+ LargeBlobsRequest();
+
+ base::Optional<int64_t> get_;
+ base::Optional<std::vector<uint8_t>> set_;
+ int64_t offset_ = 0;
+ base::Optional<int64_t> length_;
+ base::Optional<std::vector<uint8_t>> pin_uv_auth_param_;
+ base::Optional<int64_t> pin_uv_auth_protocol_;
+};
+
+class LargeBlobsResponse {
+ public:
+ LargeBlobsResponse(const LargeBlobsResponse&) = delete;
+ LargeBlobsResponse operator=(const LargeBlobsResponse&) = delete;
+ LargeBlobsResponse(LargeBlobsResponse&& other);
+ LargeBlobsResponse& operator=(LargeBlobsResponse&&);
+ ~LargeBlobsResponse();
+
+ static base::Optional<LargeBlobsResponse> ParseForRead(
+ size_t bytes_to_read,
+ const base::Optional<cbor::Value>& cbor_response);
+ static base::Optional<LargeBlobsResponse> ParseForWrite(
+ const base::Optional<cbor::Value>& cbor_response);
+
+ base::Optional<std::vector<uint8_t>> config() { return config_; }
+
+ private:
+ explicit LargeBlobsResponse(
+ base::Optional<std::vector<uint8_t>> config = base::nullopt);
+
+ base::Optional<std::vector<uint8_t>> config_;
+};
+
+// Represents the large-blob map structure
+// https://drafts.fidoalliance.org/fido-2/stable-links-to-latest/fido-client-to-authenticator-protocol.html#large-blob
+class COMPONENT_EXPORT(DEVICE_FIDO) LargeBlobData {
+ public:
+ static base::Optional<LargeBlobData> Parse(const cbor::Value& cbor_response);
+
+ LargeBlobData(LargeBlobKey key, std::vector<uint8_t> blob);
+ LargeBlobData(const LargeBlobData&) = delete;
+ LargeBlobData operator=(const LargeBlobData&) = delete;
+ LargeBlobData(LargeBlobData&&);
+ LargeBlobData& operator=(LargeBlobData&&);
+ ~LargeBlobData();
+ bool operator==(const LargeBlobData&) const;
+
+ base::Optional<std::vector<uint8_t>> Decrypt(LargeBlobKey key) const;
+ cbor::Value::MapValue AsCBOR() const;
+
+ private:
+ LargeBlobData(std::vector<uint8_t> ciphertext,
+ base::span<const uint8_t, kLargeBlobArrayNonceLength> nonce,
+ int64_t orig_size);
+ std::vector<uint8_t> ciphertext_;
+ std::array<uint8_t, kLargeBlobArrayNonceLength> nonce_;
+ int64_t orig_size_;
+};
+
+// Reading large blob arrays is done in chunks. This class provides facilities
+// to assemble together those chunks.
+class COMPONENT_EXPORT(DEVICE_FIDO) LargeBlobArrayReader {
+ public:
+ LargeBlobArrayReader();
+ LargeBlobArrayReader(const LargeBlobArrayReader&) = delete;
+ LargeBlobArrayReader operator=(const LargeBlobArrayReader&) = delete;
+ LargeBlobArrayReader(LargeBlobArrayReader&&);
+ ~LargeBlobArrayReader();
+
+ // Appends a fragment to the large blob array.
+ void Append(const std::vector<uint8_t>& fragment);
+
+ // Verifies the integrity of the large blob array. This should be called after
+ // all fragments have been |Append|ed.
+ // If successful, parses and returns the array.
+ base::Optional<std::vector<LargeBlobData>> Materialize();
+
+ // Returns the current size of the array fragments.
+ size_t size() const { return bytes_.size(); }
+
+ private:
+ std::vector<uint8_t> bytes_;
+};
+
+// Writing large blob arrays is done in chunks. This class provides facilities
+// to divide a blob into chunks.
+class COMPONENT_EXPORT(DEVICE_FIDO) LargeBlobArrayWriter {
+ public:
+ explicit LargeBlobArrayWriter(
+ const std::vector<LargeBlobData>& large_blob_array);
+ LargeBlobArrayWriter(const LargeBlobArrayWriter&) = delete;
+ LargeBlobArrayWriter operator=(const LargeBlobArrayWriter&) = delete;
+ LargeBlobArrayWriter(LargeBlobArrayWriter&&);
+ ~LargeBlobArrayWriter();
+
+ // Extracts a fragment with |length|. Can only be called if
+ // has_remaining_fragments() is true.
+ LargeBlobArrayFragment Pop(size_t length);
+
+ // Returns the current size of the array fragments.
+ size_t size() const { return bytes_.size(); }
+
+ // Returns true if there are remaining fragments to be written, false
+ // otherwise.
+ bool has_remaining_fragments() const { return offset_ < size(); }
+
+ void set_bytes_for_testing(std::vector<uint8_t> bytes) {
+ bytes_ = std::move(bytes);
+ }
+
+ private:
+ std::vector<uint8_t> bytes_;
+ size_t offset_ = 0;
+};
+
+std::pair<CtapRequestCommand, base::Optional<cbor::Value>>
+AsCTAPRequestValuePair(const LargeBlobsRequest& request);
+
+} // namespace device
+
+#endif // DEVICE_FIDO_LARGE_BLOB_H_
diff --git a/chromium/device/fido/large_blob_unittest.cc b/chromium/device/fido/large_blob_unittest.cc
new file mode 100644
index 00000000000..7fda7edd8ed
--- /dev/null
+++ b/chromium/device/fido/large_blob_unittest.cc
@@ -0,0 +1,144 @@
+// 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/large_blob.h"
+
+#include "base/optional.h"
+#include "device/fido/fido_parsing_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace device {
+
+namespace {
+
+class FidoLargeBlobTest : public testing::Test {};
+
+// An empty CBOR array (0x80) followed by LEFT(SHA-256(h'80'), 16).
+const std::array<uint8_t, 17> kValidEmptyLargeBlobArray = {
+ 0x80, 0x76, 0xbe, 0x8b, 0x52, 0x8d, 0x00, 0x75, 0xf7,
+ 0xaa, 0xe9, 0x8d, 0x6f, 0xa5, 0x7a, 0x6d, 0x3c};
+
+// Something that is not an empty CBOR array (0x10) followed by
+// LEFT(SHA-256(h'10'), 16).
+const std::array<uint8_t, 17> kInvalidLargeBlobArray = {
+ 0x10, 0xc5, 0x55, 0xea, 0xb4, 0x5d, 0x08, 0x84, 0x5a,
+ 0xe9, 0xf1, 0x0d, 0x45, 0x2a, 0x99, 0xbf, 0xcb};
+
+// An "valid" CBOR large blob array with two entries. The first entry is not a
+// valid large blob map structure. The second entry is valid.
+const std::array<uint8_t, 45> kValidLargeBlobArray = {
+ 0x82, 0xA2, 0x02, 0x42, 0x11, 0x11, 0x03, 0x02, 0xA3, 0x01, 0x42, 0x22,
+ 0x22, 0x02, 0x4C, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+ 0x30, 0x31, 0x32, 0x03, 0x02, 0x9b, 0x33, 0x75, 0x6c, 0x0a, 0x84, 0xdf,
+ 0x32, 0xcc, 0xd0, 0xc8, 0x96, 0xea, 0xa7, 0x99, 0x13};
+
+TEST_F(FidoLargeBlobTest, VerifyLargeBlobArrayIntegrityValid) {
+ std::vector<uint8_t> large_blob_array =
+ fido_parsing_utils::Materialize(kValidEmptyLargeBlobArray);
+ EXPECT_TRUE(VerifyLargeBlobArrayIntegrity(large_blob_array));
+}
+
+TEST_F(FidoLargeBlobTest, VerifyLargeBlobArrayIntegrityInvalid) {
+ std::vector<uint8_t> large_blob_array =
+ fido_parsing_utils::Materialize(kValidEmptyLargeBlobArray);
+ large_blob_array[0] += 1;
+ EXPECT_FALSE(VerifyLargeBlobArrayIntegrity(large_blob_array));
+
+ large_blob_array = fido_parsing_utils::Materialize(kValidEmptyLargeBlobArray);
+ large_blob_array.erase(large_blob_array.begin());
+ EXPECT_FALSE(VerifyLargeBlobArrayIntegrity(large_blob_array));
+}
+
+TEST_F(FidoLargeBlobTest, LargeBlobArrayReader_MaterializeEmpty) {
+ LargeBlobArrayReader large_blob_array_reader;
+ large_blob_array_reader.Append(
+ fido_parsing_utils::Materialize(kValidEmptyLargeBlobArray));
+ EXPECT_EQ(0u, large_blob_array_reader.Materialize()->size());
+}
+
+TEST_F(FidoLargeBlobTest, LargeBlobArrayReader_MaterializeInvalidCbor) {
+ LargeBlobArrayReader large_blob_array_reader;
+ large_blob_array_reader.Append(
+ fido_parsing_utils::Materialize(kInvalidLargeBlobArray));
+ EXPECT_FALSE(large_blob_array_reader.Materialize());
+}
+
+TEST_F(FidoLargeBlobTest, LargeBlobArrayReader_MaterializeInvalidHash) {
+ std::vector<uint8_t> large_blob_array =
+ fido_parsing_utils::Materialize(kValidEmptyLargeBlobArray);
+ large_blob_array[0] += 1;
+ LargeBlobArrayReader large_blob_array_reader;
+ large_blob_array_reader.Append(
+ fido_parsing_utils::Materialize(large_blob_array));
+ EXPECT_FALSE(large_blob_array_reader.Materialize());
+}
+
+TEST_F(FidoLargeBlobTest, LargeBlobArrayReader_MaterializeValid) {
+ LargeBlobArrayReader large_blob_array_reader;
+ large_blob_array_reader.Append(
+ fido_parsing_utils::Materialize(kValidLargeBlobArray));
+ std::vector<LargeBlobData> vector = *large_blob_array_reader.Materialize();
+ EXPECT_EQ(1u, vector.size());
+}
+
+// Test popping the large blob array in a fragment size that does not evenly
+// divide the length of the array.
+TEST_F(FidoLargeBlobTest, LargeBlobArrayWriter_PopUnevenly) {
+ const size_t fragment_size = 8;
+ const size_t expected_fragments =
+ kValidLargeBlobArray.size() / fragment_size + 1;
+ size_t fragments = 0;
+ ASSERT_NE(0u, kValidLargeBlobArray.size() % fragment_size);
+
+ LargeBlobArrayWriter large_blob_array_writer({});
+ std::vector<uint8_t> large_blob_array =
+ fido_parsing_utils::Materialize(kValidLargeBlobArray);
+ large_blob_array_writer.set_bytes_for_testing(large_blob_array);
+ std::vector<uint8_t> reconstructed;
+ EXPECT_TRUE(large_blob_array_writer.has_remaining_fragments());
+ while (large_blob_array_writer.has_remaining_fragments()) {
+ LargeBlobArrayFragment fragment =
+ large_blob_array_writer.Pop(fragment_size);
+ ++fragments;
+ reconstructed.insert(reconstructed.end(), fragment.bytes.begin(),
+ fragment.bytes.end());
+ EXPECT_EQ(fragments != expected_fragments,
+ large_blob_array_writer.has_remaining_fragments());
+ }
+
+ EXPECT_EQ(expected_fragments, fragments);
+ EXPECT_EQ(large_blob_array, reconstructed);
+}
+
+// Test popping the large blob array in a fragment size that evenly divides the
+// length of the array.
+TEST_F(FidoLargeBlobTest, LargeBlobArrayFragments_PopEvenly) {
+ const size_t fragment_size = 9;
+ const size_t expected_fragments = kValidLargeBlobArray.size() / fragment_size;
+ size_t fragments = 0;
+ ASSERT_EQ(0u, kValidLargeBlobArray.size() % fragment_size);
+
+ LargeBlobArrayWriter large_blob_array_writer({});
+ std::vector<uint8_t> large_blob_array =
+ fido_parsing_utils::Materialize(kValidLargeBlobArray);
+ large_blob_array_writer.set_bytes_for_testing(large_blob_array);
+ std::vector<uint8_t> reconstructed;
+ EXPECT_TRUE(large_blob_array_writer.has_remaining_fragments());
+ while (large_blob_array_writer.has_remaining_fragments()) {
+ LargeBlobArrayFragment fragment =
+ large_blob_array_writer.Pop(fragment_size);
+ ++fragments;
+ reconstructed.insert(reconstructed.end(), fragment.bytes.begin(),
+ fragment.bytes.end());
+ EXPECT_EQ(fragments != expected_fragments,
+ large_blob_array_writer.has_remaining_fragments());
+ }
+
+ EXPECT_EQ(expected_fragments, fragments);
+ EXPECT_EQ(large_blob_array, reconstructed);
+}
+
+} // namespace
+
+} // namespace device
diff --git a/chromium/device/fido/mac/browsing_data_deletion_unittest.mm b/chromium/device/fido/mac/browsing_data_deletion_unittest.mm
index 518b517c5cb..84948030afc 100644
--- a/chromium/device/fido/mac/browsing_data_deletion_unittest.mm
+++ b/chromium/device/fido/mac/browsing_data_deletion_unittest.mm
@@ -54,12 +54,13 @@ const std::vector<uint8_t> kUserId = {10, 11, 12, 13, 14, 15};
// credentials in the non-legacy keychain that are tagged with the keychain
// access group used in this test.
base::ScopedCFTypeRef<CFMutableDictionaryRef> BaseQuery() {
- base::ScopedCFTypeRef<CFMutableDictionaryRef> query(
- CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr));
+ base::ScopedCFTypeRef<CFMutableDictionaryRef> query(CFDictionaryCreateMutable(
+ kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
CFDictionarySetValue(query, kSecClass, kSecClassKey);
base::ScopedCFTypeRef<CFStringRef> access_group_ref(
base::SysUTF8ToCFStringRef(kKeychainAccessGroup));
- CFDictionarySetValue(query, kSecAttrAccessGroup, access_group_ref.release());
+ CFDictionarySetValue(query, kSecAttrAccessGroup, access_group_ref);
CFDictionarySetValue(query, kSecAttrNoLegacy, @YES);
CFDictionarySetValue(query, kSecReturnAttributes, @YES);
CFDictionarySetValue(query, kSecMatchLimit, kSecMatchLimitAll);
diff --git a/chromium/device/fido/mac/credential_store.mm b/chromium/device/fido/mac/credential_store.mm
index ebcce170ce5..a373dfafc6e 100644
--- a/chromium/device/fido/mac/credential_store.mm
+++ b/chromium/device/fido/mac/credential_store.mm
@@ -32,8 +32,9 @@ namespace {
base::ScopedCFTypeRef<CFMutableDictionaryRef> DefaultKeychainQuery(
const AuthenticatorConfig& config,
const std::string& rp_id) {
- base::ScopedCFTypeRef<CFMutableDictionaryRef> query(
- CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr));
+ base::ScopedCFTypeRef<CFMutableDictionaryRef> query(CFDictionaryCreateMutable(
+ kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
CFDictionarySetValue(query, kSecClass, kSecClassKey);
CFDictionarySetValue(query, kSecAttrAccessGroup,
base::SysUTF8ToNSString(config.keychain_access_group));
@@ -79,8 +80,9 @@ QueryKeychainItemsForProfile(const std::string& keychain_access_group,
// keychain access group.
std::vector<base::ScopedCFTypeRef<CFDictionaryRef>> result;
- base::ScopedCFTypeRef<CFMutableDictionaryRef> query(
- CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr));
+ base::ScopedCFTypeRef<CFMutableDictionaryRef> query(CFDictionaryCreateMutable(
+ kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
CFDictionarySetValue(query, kSecClass, kSecClassKey);
CFDictionarySetValue(query, kSecAttrAccessGroup,
base::SysUTF8ToNSString(keychain_access_group));
@@ -178,7 +180,9 @@ bool DoDeleteWebAuthnCredentials(const std::string& keychain_access_group,
continue;
}
base::ScopedCFTypeRef<CFMutableDictionaryRef> delete_query(
- CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr));
+ CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
CFDictionarySetValue(delete_query, kSecClass, kSecClassKey);
CFDictionarySetValue(delete_query, kSecAttrApplicationLabel,
sec_attr_app_label);
@@ -233,7 +237,9 @@ TouchIdCredentialStore::CreateCredential(
CredentialMetadata::FromPublicKeyCredentialUserEntity(user, is_resident));
base::ScopedCFTypeRef<CFMutableDictionaryRef> params(
- CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr));
+ CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
CFDictionarySetValue(params, kSecAttrKeyType,
kSecAttrKeyTypeECSECPrimeRandom);
CFDictionarySetValue(params, kSecAttrKeySizeInBits, @256);
diff --git a/chromium/device/fido/mac/make_credential_operation.mm b/chromium/device/fido/mac/make_credential_operation.mm
index fc27e4413fa..c8e0e310271 100644
--- a/chromium/device/fido/mac/make_credential_operation.mm
+++ b/chromium/device/fido/mac/make_credential_operation.mm
@@ -149,6 +149,7 @@ void MakeCredentialOperation::PromptTouchIdDone(bool success) {
std::make_unique<PackedAttestationStatement>(
CoseAlgorithmIdentifier::kEs256, std::move(*signature),
/*x509_certificates=*/std::vector<std::vector<uint8_t>>())));
+ response.is_resident_key = request_.resident_key_required;
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 f9af4eb253a..82ee3860776 100644
--- a/chromium/device/fido/mac/touch_id_context.mm
+++ b/chromium/device/fido/mac/touch_id_context.mm
@@ -79,7 +79,9 @@ bool CanCreateSecureEnclaveKeyPair() {
// bindings. Instead, attempt to create an ephemeral key pair in the secure
// enclave.
base::ScopedCFTypeRef<CFMutableDictionaryRef> params(
- CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr));
+ CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks));
CFDictionarySetValue(params, kSecAttrKeyType,
kSecAttrKeyTypeECSECPrimeRandom);
CFDictionarySetValue(params, kSecAttrKeySizeInBits, @256);
diff --git a/chromium/device/fido/make_credential_handler_unittest.cc b/chromium/device/fido/make_credential_handler_unittest.cc
index 3801a68a00f..2de00096f4b 100644
--- a/chromium/device/fido/make_credential_handler_unittest.cc
+++ b/chromium/device/fido/make_credential_handler_unittest.cc
@@ -64,14 +64,8 @@ class FidoMakeCredentialHandlerTest : public ::testing::Test {
platform_discovery_ = fake_discovery_factory_->ForgeNextPlatformDiscovery();
}
- std::unique_ptr<MakeCredentialRequestHandler> CreateMakeCredentialHandler() {
- return CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
- AuthenticatorSelectionCriteria());
- }
-
- std::unique_ptr<MakeCredentialRequestHandler>
- CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
- AuthenticatorSelectionCriteria authenticator_selection_criteria) {
+ std::unique_ptr<MakeCredentialRequestHandler> CreateMakeCredentialHandler(
+ AuthenticatorSelectionCriteria authenticator_selection_criteria = {}) {
ForgeDiscoveries();
PublicKeyCredentialRpEntity rp(test_data::kRelyingPartyId);
PublicKeyCredentialUserEntity user(
@@ -83,13 +77,13 @@ class FidoMakeCredentialHandlerTest : public ::testing::Test {
test_data::kClientDataJson, std::move(rp), std::move(user),
std::move(credential_params));
- MakeCredentialRequestHandler::Options options;
+ MakeCredentialRequestHandler::Options options(
+ authenticator_selection_criteria);
options.allow_skipping_pin_touch = true;
auto handler = std::make_unique<MakeCredentialRequestHandler>(
fake_discovery_factory_.get(), supported_transports_,
- std::move(request_parameter),
- std::move(authenticator_selection_criteria), options, cb_.callback());
+ std::move(request_parameter), std::move(options), cb_.callback());
if (pending_mock_platform_device_) {
platform_discovery_->AddDevice(std::move(pending_mock_platform_device_));
platform_discovery_->WaitForCallToStartAndSimulateSuccess();
@@ -189,10 +183,9 @@ TEST_F(FidoMakeCredentialHandlerTest, TestU2fRegister) {
TEST_F(FidoMakeCredentialHandlerTest, U2fRegisterWithUserVerificationRequired) {
auto request_handler =
- CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
- AuthenticatorSelectionCriteria(
- AuthenticatorAttachment::kAny, /*require_resident_key=*/false,
- UserVerificationRequirement::kRequired));
+ CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
+ AuthenticatorAttachment::kAny, ResidentKeyRequirement::kDiscouraged,
+ UserVerificationRequirement::kRequired));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = MockFidoDevice::MakeU2fWithGetInfoExpectation();
@@ -208,10 +201,9 @@ TEST_F(FidoMakeCredentialHandlerTest, U2fRegisterWithUserVerificationRequired) {
TEST_F(FidoMakeCredentialHandlerTest, U2fRegisterWithResidentKeyRequirement) {
auto request_handler =
- CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
- AuthenticatorSelectionCriteria(
- AuthenticatorAttachment::kAny, /*require_resident_key=*/true,
- UserVerificationRequirement::kPreferred));
+ CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
+ AuthenticatorAttachment::kAny, ResidentKeyRequirement::kRequired,
+ UserVerificationRequirement::kPreferred));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = MockFidoDevice::MakeU2fWithGetInfoExpectation();
@@ -227,10 +219,9 @@ TEST_F(FidoMakeCredentialHandlerTest, U2fRegisterWithResidentKeyRequirement) {
TEST_F(FidoMakeCredentialHandlerTest, UserVerificationRequirementNotMet) {
auto request_handler =
- CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
- AuthenticatorSelectionCriteria(
- AuthenticatorAttachment::kAny, /*require_resident_key=*/false,
- UserVerificationRequirement::kRequired));
+ CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
+ AuthenticatorAttachment::kAny, ResidentKeyRequirement::kDiscouraged,
+ UserVerificationRequirement::kRequired));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(
@@ -247,12 +238,10 @@ TEST_F(FidoMakeCredentialHandlerTest, UserVerificationRequirementNotMet) {
}
TEST_F(FidoMakeCredentialHandlerTest, CrossPlatformAttachment) {
- auto request_handler =
- CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
- AuthenticatorSelectionCriteria(
- AuthenticatorAttachment::kCrossPlatform,
- /*require_resident_key=*/false,
- UserVerificationRequirement::kPreferred));
+ auto request_handler = CreateMakeCredentialHandler(
+ AuthenticatorSelectionCriteria(AuthenticatorAttachment::kCrossPlatform,
+ ResidentKeyRequirement::kDiscouraged,
+ UserVerificationRequirement::kPreferred));
// kCloudAssistedBluetoothLowEnergy not yet supported for MakeCredential.
ExpectAllowedTransportsForRequestAre(
@@ -271,12 +260,10 @@ TEST_F(FidoMakeCredentialHandlerTest, PlatformAttachment) {
EXPECT_CALL(*platform_device, Cancel(_));
set_mock_platform_device(std::move(platform_device));
- auto request_handler =
- CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
- AuthenticatorSelectionCriteria(
- AuthenticatorAttachment::kPlatform,
- /*require_resident_key=*/false,
- UserVerificationRequirement::kRequired));
+ auto request_handler = CreateMakeCredentialHandler(
+ AuthenticatorSelectionCriteria(AuthenticatorAttachment::kPlatform,
+ ResidentKeyRequirement::kDiscouraged,
+ UserVerificationRequirement::kRequired));
ExpectAllowedTransportsForRequestAre(request_handler.get(),
{FidoTransportProtocol::kInternal});
@@ -284,10 +271,9 @@ TEST_F(FidoMakeCredentialHandlerTest, PlatformAttachment) {
TEST_F(FidoMakeCredentialHandlerTest, ResidentKeyRequirementNotMet) {
auto request_handler =
- CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
- AuthenticatorSelectionCriteria(
- AuthenticatorAttachment::kAny, /*require_resident_key=*/true,
- UserVerificationRequirement::kPreferred));
+ CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
+ AuthenticatorAttachment::kAny, ResidentKeyRequirement::kRequired,
+ UserVerificationRequirement::kPreferred));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(
@@ -399,10 +385,9 @@ TEST_F(FidoMakeCredentialHandlerTest, ResidentKeyCancelOtherAuthenticator) {
// most important: we don't want a stray touch to create a resident credential
// on a second authenticator.
auto request_handler =
- CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
- AuthenticatorSelectionCriteria(
- AuthenticatorAttachment::kAny, /*require_resident_key=*/true,
- UserVerificationRequirement::kRequired));
+ CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
+ AuthenticatorAttachment::kAny, ResidentKeyRequirement::kRequired,
+ UserVerificationRequirement::kRequired));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device1 = MockFidoDevice::MakeCtapWithGetInfoExpectation();
@@ -433,10 +418,9 @@ TEST_F(FidoMakeCredentialHandlerTest, ResidentKeyCancel) {
// request handler is deleted. When a user cancels, we don't want a stray
// touch creating a resident key.
auto request_handler =
- CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
- AuthenticatorSelectionCriteria(
- AuthenticatorAttachment::kAny, /*require_resident_key=*/true,
- UserVerificationRequirement::kRequired));
+ CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
+ AuthenticatorAttachment::kAny, ResidentKeyRequirement::kRequired,
+ UserVerificationRequirement::kRequired));
auto delete_request_handler = [&request_handler]() {
base::ThreadTaskRunnerHandle::Get()->PostTask(
@@ -464,12 +448,10 @@ TEST_F(FidoMakeCredentialHandlerTest, ResidentKeyCancel) {
TEST_F(FidoMakeCredentialHandlerTest,
AuthenticatorSelectionCriteriaSatisfiedByCrossPlatformDevice) {
set_supported_transports({FidoTransportProtocol::kUsbHumanInterfaceDevice});
- auto request_handler =
- CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
- AuthenticatorSelectionCriteria(
- AuthenticatorAttachment::kCrossPlatform,
- /*require_resident_key=*/true,
- UserVerificationRequirement::kRequired));
+ auto request_handler = CreateMakeCredentialHandler(
+ AuthenticatorSelectionCriteria(AuthenticatorAttachment::kCrossPlatform,
+ ResidentKeyRequirement::kRequired,
+ UserVerificationRequirement::kRequired));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation();
@@ -504,11 +486,9 @@ TEST_F(FidoMakeCredentialHandlerTest,
set_mock_platform_device(std::move(platform_device));
auto request_handler =
- CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
- AuthenticatorSelectionCriteria(
- AuthenticatorAttachment::kPlatform,
- /*require_resident_key=*/true,
- UserVerificationRequirement::kRequired));
+ CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
+ AuthenticatorAttachment::kPlatform, ResidentKeyRequirement::kRequired,
+ UserVerificationRequirement::kRequired));
callback().WaitForCallback();
EXPECT_EQ(MakeCredentialStatus::kSuccess, callback().status());
@@ -522,12 +502,10 @@ TEST_F(FidoMakeCredentialHandlerTest,
// its GetInfo response is rejected.
TEST_F(FidoMakeCredentialHandlerTest,
CrossPlatformAuthenticatorPretendingToBePlatform) {
- auto request_handler =
- CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
- AuthenticatorSelectionCriteria(
- AuthenticatorAttachment::kCrossPlatform,
- /*require_resident_key=*/false,
- UserVerificationRequirement::kPreferred));
+ auto request_handler = CreateMakeCredentialHandler(
+ AuthenticatorSelectionCriteria(AuthenticatorAttachment::kCrossPlatform,
+ ResidentKeyRequirement::kDiscouraged,
+ UserVerificationRequirement::kPreferred));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation(
@@ -553,11 +531,9 @@ TEST_F(FidoMakeCredentialHandlerTest,
set_mock_platform_device(std::move(platform_device));
auto request_handler =
- CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
- AuthenticatorSelectionCriteria(
- AuthenticatorAttachment::kPlatform,
- /*require_resident_key=*/true,
- UserVerificationRequirement::kRequired));
+ CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
+ AuthenticatorAttachment::kPlatform, ResidentKeyRequirement::kRequired,
+ UserVerificationRequirement::kRequired));
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_FALSE(callback().was_called());
@@ -569,22 +545,19 @@ TEST_F(FidoMakeCredentialHandlerTest, SupportedTransportsAreOnlyNfc) {
};
set_supported_transports(kNfc);
- auto request_handler =
- CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
- AuthenticatorSelectionCriteria(
- AuthenticatorAttachment::kCrossPlatform,
- /*require_resident_key=*/false,
- UserVerificationRequirement::kPreferred));
+ auto request_handler = CreateMakeCredentialHandler(
+ AuthenticatorSelectionCriteria(AuthenticatorAttachment::kCrossPlatform,
+ ResidentKeyRequirement::kDiscouraged,
+ UserVerificationRequirement::kPreferred));
ExpectAllowedTransportsForRequestAre(request_handler.get(), kNfc);
}
TEST_F(FidoMakeCredentialHandlerTest, IncorrectRpIdHash) {
auto request_handler =
- CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
- AuthenticatorSelectionCriteria(
- AuthenticatorAttachment::kAny, /*require_resident_key=*/false,
- UserVerificationRequirement::kPreferred));
+ CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
+ AuthenticatorAttachment::kAny, ResidentKeyRequirement::kDiscouraged,
+ UserVerificationRequirement::kPreferred));
discovery()->WaitForCallToStartAndSimulateSuccess();
auto device = MockFidoDevice::MakeCtapWithGetInfoExpectation();
@@ -609,10 +582,9 @@ TEST_F(FidoMakeCredentialHandlerTest,
state->fingerprints_enrolled = true;
auto request_handler =
- CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
- AuthenticatorSelectionCriteria(
- AuthenticatorAttachment::kAny, /*require_resident_key=*/true,
- UserVerificationRequirement::kPreferred));
+ CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
+ AuthenticatorAttachment::kAny, ResidentKeyRequirement::kRequired,
+ UserVerificationRequirement::kPreferred));
discovery()->WaitForCallToStartAndSimulateSuccess();
discovery()->AddDevice(std::make_unique<VirtualCtap2Device>(
@@ -629,10 +601,9 @@ TEST_F(FidoMakeCredentialHandlerTest,
MakeCredentialFailsForIncompatibleResidentKeyOption) {
auto device = std::make_unique<VirtualCtap2Device>();
auto request_handler =
- CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
- AuthenticatorSelectionCriteria(
- AuthenticatorAttachment::kAny, /*require_resident_key=*/true,
- UserVerificationRequirement::kPreferred));
+ CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
+ AuthenticatorAttachment::kAny, ResidentKeyRequirement::kRequired,
+ UserVerificationRequirement::kPreferred));
discovery()->WaitForCallToStartAndSimulateSuccess();
discovery()->AddDevice(std::move(device));
@@ -655,12 +626,10 @@ TEST_F(FidoMakeCredentialHandlerTest,
CtapDeviceResponseCode::kCtap2ErrOperationDenied);
set_mock_platform_device(std::move(platform_device));
- auto request_handler =
- CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
- AuthenticatorSelectionCriteria(
- AuthenticatorAttachment::kPlatform,
- /*require_resident_key=*/false,
- UserVerificationRequirement::kPreferred));
+ auto request_handler = CreateMakeCredentialHandler(
+ AuthenticatorSelectionCriteria(AuthenticatorAttachment::kPlatform,
+ ResidentKeyRequirement::kDiscouraged,
+ UserVerificationRequirement::kPreferred));
task_environment_.FastForwardUntilNoTasksRemain();
EXPECT_TRUE(callback().was_called());
@@ -677,10 +646,9 @@ TEST_F(FidoMakeCredentialHandlerTest,
CtapDeviceResponseCode::kCtap2ErrOperationDenied);
auto request_handler =
- CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
- AuthenticatorSelectionCriteria(
- AuthenticatorAttachment::kAny, /*require_resident_key=*/false,
- UserVerificationRequirement::kPreferred));
+ CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
+ AuthenticatorAttachment::kAny, ResidentKeyRequirement::kDiscouraged,
+ UserVerificationRequirement::kPreferred));
discovery()->WaitForCallToStartAndSimulateSuccess();
discovery()->AddDevice(std::move(device));
@@ -707,10 +675,9 @@ TEST_F(FidoMakeCredentialHandlerTest,
IsUvRequest(true));
auto request_handler =
- CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
- AuthenticatorSelectionCriteria(
- AuthenticatorAttachment::kAny, /*require_resident_key=*/false,
- UserVerificationRequirement::kDiscouraged));
+ CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
+ AuthenticatorAttachment::kAny, ResidentKeyRequirement::kDiscouraged,
+ UserVerificationRequirement::kDiscouraged));
discovery()->AddDevice(std::move(device));
discovery()->WaitForCallToStartAndSimulateSuccess();
@@ -727,10 +694,9 @@ TEST_F(FidoMakeCredentialHandlerTest, TestRequestWithPinAuthInvalid) {
CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid);
auto request_handler =
- CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
- AuthenticatorSelectionCriteria(
- AuthenticatorAttachment::kAny, /*require_resident_key=*/false,
- UserVerificationRequirement::kPreferred));
+ CreateMakeCredentialHandler(AuthenticatorSelectionCriteria(
+ AuthenticatorAttachment::kAny, ResidentKeyRequirement::kDiscouraged,
+ UserVerificationRequirement::kPreferred));
discovery()->WaitForCallToStartAndSimulateSuccess();
discovery()->AddDevice(std::move(device));
diff --git a/chromium/device/fido/make_credential_request_handler.cc b/chromium/device/fido/make_credential_request_handler.cc
index 30c96104071..19720a51ac2 100644
--- a/chromium/device/fido/make_credential_request_handler.cc
+++ b/chromium/device/fido/make_credential_request_handler.cc
@@ -79,7 +79,7 @@ base::Optional<MakeCredentialStatus> ConvertDeviceResponseCode(
// should even blink for a request.
bool IsCandidateAuthenticatorPreTouch(
FidoAuthenticator* authenticator,
- const AuthenticatorSelectionCriteria& authenticator_selection_criteria) {
+ AuthenticatorAttachment requested_attachment) {
const auto& opt_options = authenticator->Options();
if (!opt_options) {
// This authenticator doesn't know its capabilities yet, so we need
@@ -88,11 +88,9 @@ bool IsCandidateAuthenticatorPreTouch(
return true;
}
- if ((authenticator_selection_criteria.authenticator_attachment() ==
- AuthenticatorAttachment::kPlatform &&
+ if ((requested_attachment == AuthenticatorAttachment::kPlatform &&
!opt_options->is_platform_device) ||
- (authenticator_selection_criteria.authenticator_attachment() ==
- AuthenticatorAttachment::kCrossPlatform &&
+ (requested_attachment == AuthenticatorAttachment::kCrossPlatform &&
opt_options->is_platform_device)) {
return false;
}
@@ -106,14 +104,14 @@ MakeCredentialStatus IsCandidateAuthenticatorPostTouch(
const CtapMakeCredentialRequest& request,
FidoAuthenticator* authenticator,
const MakeCredentialRequestHandler::Options& options,
- const AuthenticatorSelectionCriteria& authenticator_selection_criteria,
const FidoRequestHandlerBase::Observer* observer) {
if (options.cred_protect_request && options.cred_protect_request->second &&
!authenticator->SupportsCredProtectExtension()) {
return MakeCredentialStatus::kAuthenticatorMissingResidentKeys;
}
- const auto& auth_options = authenticator->Options();
+ const base::Optional<AuthenticatorSupportedOptions>& auth_options =
+ authenticator->Options();
if (!auth_options) {
// This authenticator doesn't know its capabilities yet, so we need
// to assume it can handle the request. This is the case for Windows,
@@ -121,7 +119,7 @@ MakeCredentialStatus IsCandidateAuthenticatorPostTouch(
return MakeCredentialStatus::kSuccess;
}
- if (authenticator_selection_criteria.require_resident_key() &&
+ if (options.resident_key == ResidentKeyRequirement::kRequired &&
!auth_options->supports_resident_key) {
return MakeCredentialStatus::kAuthenticatorMissingResidentKeys;
}
@@ -160,10 +158,8 @@ MakeCredentialStatus IsCandidateAuthenticatorPostTouch(
}
base::flat_set<FidoTransportProtocol> GetTransportsAllowedByRP(
- const AuthenticatorSelectionCriteria& authenticator_selection_criteria) {
- const auto attachment_type =
- authenticator_selection_criteria.authenticator_attachment();
- switch (attachment_type) {
+ AuthenticatorAttachment authenticator_attachment) {
+ switch (authenticator_attachment) {
case AuthenticatorAttachment::kPlatform:
return {FidoTransportProtocol::kInternal};
case AuthenticatorAttachment::kCrossPlatform:
@@ -317,6 +313,11 @@ bool ResponseValid(const FidoAuthenticator& authenticator,
return false;
}
+ if (request.large_blob_key && !response.large_blob_key()) {
+ FIDO_LOG(ERROR) << "Large blob key requested but not returned";
+ return false;
+ }
+
return true;
}
} // namespace
@@ -324,26 +325,37 @@ bool ResponseValid(const FidoAuthenticator& authenticator,
MakeCredentialRequestHandler::Options::Options() = default;
MakeCredentialRequestHandler::Options::~Options() = default;
MakeCredentialRequestHandler::Options::Options(const Options&) = default;
+MakeCredentialRequestHandler::Options::Options(
+ const AuthenticatorSelectionCriteria& authenticator_selection_criteria)
+ : authenticator_attachment(
+ authenticator_selection_criteria.authenticator_attachment()),
+ resident_key(authenticator_selection_criteria.resident_key()),
+ user_verification(
+ authenticator_selection_criteria.user_verification_requirement()) {}
+MakeCredentialRequestHandler::Options::Options(Options&&) = default;
+MakeCredentialRequestHandler::Options&
+MakeCredentialRequestHandler::Options::operator=(const Options&) = default;
+MakeCredentialRequestHandler::Options&
+MakeCredentialRequestHandler::Options::operator=(Options&&) = default;
MakeCredentialRequestHandler::MakeCredentialRequestHandler(
FidoDiscoveryFactory* fido_discovery_factory,
const base::flat_set<FidoTransportProtocol>& supported_transports,
CtapMakeCredentialRequest request,
- AuthenticatorSelectionCriteria authenticator_selection_criteria,
const Options& options,
CompletionCallback completion_callback)
: FidoRequestHandlerBase(
fido_discovery_factory,
base::STLSetIntersection<base::flat_set<FidoTransportProtocol>>(
supported_transports,
- GetTransportsAllowedByRP(authenticator_selection_criteria))),
+ GetTransportsAllowedByRP(options.authenticator_attachment))),
completion_callback_(std::move(completion_callback)),
request_(std::move(request)),
- authenticator_selection_criteria_(
- std::move(authenticator_selection_criteria)),
options_(options) {
// These parts of the request should be filled in by
// |SpecializeRequestForAuthenticator|.
+ DCHECK_EQ(request_.authenticator_attachment, AuthenticatorAttachment::kAny);
+ DCHECK(!request_.resident_key_required);
DCHECK(!request_.cred_protect);
DCHECK(!request_.android_client_data_ext);
DCHECK(!request_.cred_protect_enforce);
@@ -351,21 +363,6 @@ MakeCredentialRequestHandler::MakeCredentialRequestHandler(
transport_availability_info().request_type =
FidoRequestHandlerBase::RequestType::kMakeCredential;
- // Set the rk, uv and attachment fields, which were only initialized to
- // default values up to here. TODO(martinkr): Initialize these fields earlier
- // (in AuthenticatorImpl) and get rid of the separate
- // AuthenticatorSelectionCriteriaParameter.
- if (authenticator_selection_criteria_.require_resident_key()) {
- request_.resident_key_required = true;
- request_.user_verification = UserVerificationRequirement::kRequired;
- } else {
- request_.resident_key_required = false;
- request_.user_verification =
- authenticator_selection_criteria_.user_verification_requirement();
- }
- request_.authenticator_attachment =
- authenticator_selection_criteria_.authenticator_attachment();
-
Start();
}
@@ -377,7 +374,7 @@ void MakeCredentialRequestHandler::DispatchRequest(
if (state_ != State::kWaitingForTouch ||
!IsCandidateAuthenticatorPreTouch(authenticator,
- authenticator_selection_criteria_)) {
+ options_.authenticator_attachment)) {
return;
}
@@ -386,7 +383,6 @@ void MakeCredentialRequestHandler::DispatchRequest(
SpecializeRequestForAuthenticator(request.get(), authenticator);
if (IsCandidateAuthenticatorPostTouch(*request.get(), authenticator, options_,
- authenticator_selection_criteria_,
observer()) !=
MakeCredentialStatus::kSuccess) {
#if defined(OS_WIN)
@@ -457,12 +453,9 @@ void MakeCredentialRequestHandler::DispatchRequest(
if (!request->is_u2f_only &&
request->user_verification != UserVerificationRequirement::kDiscouraged &&
authenticator->CanGetUvToken()) {
- base::Optional<std::string> rp_id(request->rp.id);
- authenticator->GetUvToken(
- std::move(rp_id),
- base::BindOnce(&MakeCredentialRequestHandler::OnHaveUvToken,
- weak_factory_.GetWeakPtr(), authenticator,
- std::move(request)));
+ authenticator->GetUvRetries(base::BindOnce(
+ &MakeCredentialRequestHandler::OnStartUvTokenOrFallback,
+ weak_factory_.GetWeakPtr(), authenticator, std::move(request)));
return;
}
@@ -561,6 +554,24 @@ void MakeCredentialRequestHandler::HandleResponse(
return;
}
+ if (options_.resident_key == ResidentKeyRequirement::kPreferred &&
+ request->resident_key_required &&
+ status == CtapDeviceResponseCode::kCtap2ErrKeyStoreFull) {
+ // TODO(crbug/1117630): This probably requires a second touch and we should
+ // add UI for that. PR #962 aims to change CTAP2.1 to return this error
+ // before UP, so we might need to gate this on the supported CTAP version.
+ FIDO_LOG(DEBUG) << "Downgrading rk=preferred to non-resident credential "
+ "because key storage is full";
+ request->resident_key_required = false;
+ CtapMakeCredentialRequest request_copy(*request);
+ authenticator->MakeCredential(
+ std::move(request_copy),
+ base::BindOnce(&MakeCredentialRequestHandler::HandleResponse,
+ weak_factory_.GetWeakPtr(), authenticator,
+ std::move(request), base::ElapsedTimer()));
+ return;
+ }
+
const base::Optional<MakeCredentialStatus> maybe_result =
ConvertDeviceResponseCode(status);
if (!maybe_result) {
@@ -666,7 +677,6 @@ void MakeCredentialRequestHandler::HandleInapplicableAuthenticator(
CancelActiveAuthenticators(authenticator->GetId());
const MakeCredentialStatus capability_error =
IsCandidateAuthenticatorPostTouch(*request.get(), authenticator, options_,
- authenticator_selection_criteria_,
observer());
DCHECK_NE(capability_error, MakeCredentialStatus::kSuccess);
std::move(completion_callback_).Run(capability_error, base::nullopt, nullptr);
@@ -851,6 +861,42 @@ void MakeCredentialRequestHandler::OnEnrollmentComplete(
DispatchRequestWithToken(std::move(request), std::move(token));
}
+void MakeCredentialRequestHandler::OnStartUvTokenOrFallback(
+ FidoAuthenticator* authenticator,
+ std::unique_ptr<CtapMakeCredentialRequest> request,
+ CtapDeviceResponseCode status,
+ base::Optional<pin::RetriesResponse> response) {
+ size_t retries;
+ if (status != CtapDeviceResponseCode::kSuccess) {
+ FIDO_LOG(ERROR) << "OnStartUvTokenOrFallback() failed for "
+ << authenticator_->GetDisplayName()
+ << ", assuming authenticator locked.";
+ retries = 0;
+ } else {
+ retries = response->retries;
+ }
+
+ if (retries == 0) {
+ if (authenticator->WillNeedPINToMakeCredential(*request, observer()) ==
+ MakeCredentialPINDisposition::kUsePINForFallback) {
+ authenticator->GetTouch(base::BindOnce(
+ &MakeCredentialRequestHandler::StartPINFallbackForInternalUv,
+ weak_factory_.GetWeakPtr(), authenticator, std::move(request)));
+ return;
+ }
+ authenticator->GetTouch(
+ base::BindOnce(&MakeCredentialRequestHandler::HandleInternalUvLocked,
+ weak_factory_.GetWeakPtr(), authenticator));
+ }
+
+ base::Optional<std::string> rp_id(request->rp.id);
+ authenticator->GetUvToken(
+ std::move(rp_id),
+ base::BindOnce(&MakeCredentialRequestHandler::OnHaveUvToken,
+ weak_factory_.GetWeakPtr(), authenticator,
+ std::move(request)));
+}
+
void MakeCredentialRequestHandler::OnUvRetriesResponse(
std::unique_ptr<CtapMakeCredentialRequest> request,
CtapDeviceResponseCode status,
@@ -894,28 +940,19 @@ void MakeCredentialRequestHandler::OnHaveUvToken(
return;
}
- if (status == CtapDeviceResponseCode::kCtap2ErrPinInvalid ||
+ if (status == CtapDeviceResponseCode::kCtap2ErrUvInvalid ||
status == CtapDeviceResponseCode::kCtap2ErrOperationDenied ||
status == CtapDeviceResponseCode::kCtap2ErrUvBlocked) {
if (status == CtapDeviceResponseCode::kCtap2ErrUvBlocked) {
- // This error is returned immediately without user interaction. Ask for a
- // touch and fall back to PIN.
- FIDO_LOG(DEBUG) << "Internal UV blocked for "
- << authenticator->GetDisplayName()
- << ", falling back to PIN.";
if (authenticator->WillNeedPINToMakeCredential(*request, observer()) ==
MakeCredentialPINDisposition::kUsePINForFallback) {
- authenticator->GetTouch(base::BindOnce(
- &MakeCredentialRequestHandler::StartPINFallbackForInternalUv,
- weak_factory_.GetWeakPtr(), authenticator, std::move(request)));
+ StartPINFallbackForInternalUv(authenticator, std::move(request));
return;
}
- authenticator->GetTouch(
- base::BindOnce(&MakeCredentialRequestHandler::HandleInternalUvLocked,
- weak_factory_.GetWeakPtr(), authenticator));
+ HandleInternalUvLocked(authenticator);
return;
}
- DCHECK(status == CtapDeviceResponseCode::kCtap2ErrPinInvalid ||
+ DCHECK(status == CtapDeviceResponseCode::kCtap2ErrUvInvalid ||
status == CtapDeviceResponseCode::kCtap2ErrOperationDenied);
CancelActiveAuthenticators(authenticator->GetId());
authenticator_ = authenticator;
@@ -958,6 +995,40 @@ void MakeCredentialRequestHandler::DispatchRequestWithToken(
void MakeCredentialRequestHandler::SpecializeRequestForAuthenticator(
CtapMakeCredentialRequest* request,
const FidoAuthenticator* authenticator) {
+ // Only Windows cares about |authenticator_attachment| on the request.
+ request->authenticator_attachment = options_.authenticator_attachment;
+
+ const base::Optional<AuthenticatorSupportedOptions>& auth_options =
+ authenticator->Options();
+ switch (options_.resident_key) {
+ case ResidentKeyRequirement::kRequired:
+ request->resident_key_required = true;
+ request->user_verification = UserVerificationRequirement::kRequired;
+ break;
+ case ResidentKeyRequirement::kPreferred: {
+ // Create a resident key if the authenticator supports it and the UI is
+ // capable of prompting for PIN/UV.
+ request->resident_key_required =
+#if defined(OS_WIN)
+ // Windows does not yet support rk=preferred.
+ !authenticator->IsWinNativeApiAuthenticator() &&
+#endif
+ auth_options && auth_options->supports_resident_key &&
+ (observer()->SupportsPIN() ||
+ auth_options->user_verification_availability ==
+ AuthenticatorSupportedOptions::UserVerificationAvailability::
+ kSupportedAndConfigured);
+ break;
+ }
+ case ResidentKeyRequirement::kDiscouraged:
+ request->resident_key_required = false;
+ break;
+ }
+
+ request->user_verification = request->resident_key_required
+ ? UserVerificationRequirement::kRequired
+ : options_.user_verification;
+
if (options_.cred_protect_request &&
authenticator->SupportsCredProtectExtension()) {
request->cred_protect = CredProtectForAuthenticator(
@@ -965,8 +1036,8 @@ void MakeCredentialRequestHandler::SpecializeRequestForAuthenticator(
request->cred_protect_enforce = options_.cred_protect_request->second;
}
- if (options_.android_client_data_ext && authenticator->Options() &&
- authenticator->Options()->supports_android_client_data_ext) {
+ if (options_.android_client_data_ext && auth_options &&
+ auth_options->supports_android_client_data_ext) {
request->android_client_data_ext = *options_.android_client_data_ext;
}
@@ -974,6 +1045,11 @@ void MakeCredentialRequestHandler::SpecializeRequestForAuthenticator(
request->hmac_secret = false;
}
+ if (request->large_blob_key &&
+ !authenticator->Options()->supports_large_blobs) {
+ request->large_blob_key = false;
+ }
+
if (!authenticator->SupportsEnterpriseAttestation()) {
switch (request->attestation_preference) {
case AttestationConveyancePreference::kEnterpriseApprovedByBrowser:
diff --git a/chromium/device/fido/make_credential_request_handler.h b/chromium/device/fido/make_credential_request_handler.h
index 6f42a9c56b0..2d3682bd224 100644
--- a/chromium/device/fido/make_credential_request_handler.h
+++ b/chromium/device/fido/make_credential_request_handler.h
@@ -76,24 +76,48 @@ class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialRequestHandler
// |CtapMakeCredentialRequest|.
struct COMPONENT_EXPORT(DEVICE_FIDO) Options {
Options();
+ explicit Options(
+ const AuthenticatorSelectionCriteria& authenticator_selection_criteria);
~Options();
Options(const Options&);
+ Options(Options&&);
+ Options& operator=(const Options&);
+ Options& operator=(Options&&);
- bool allow_skipping_pin_touch = false;
- base::Optional<AndroidClientDataExtensionInput> android_client_data_ext;
+ // authenticator_attachment is a constraint on the type of authenticator
+ // that a credential should be created on.
+ AuthenticatorAttachment authenticator_attachment =
+ AuthenticatorAttachment::kAny;
+
+ // resident_key indicates whether the request should result in the creation
+ // of a client-side discoverable credential (aka resident key).
+ ResidentKeyRequirement resident_key = ResidentKeyRequirement::kDiscouraged;
+
+ // user_verification indicates whether the authenticator should (or must)
+ // perform user verficiation before creating the credential.
+ UserVerificationRequirement user_verification =
+ UserVerificationRequirement::kPreferred;
// cred_protect_request extends |CredProtect| to include information that
// applies at request-routing time. The second element is true if the
// indicated protection level must be provided by the target authenticator
// for the MakeCredential request to be sent.
base::Optional<std::pair<CredProtectRequest, bool>> cred_protect_request;
+
+ // allow_skipping_pin_touch causes the handler to forego the first
+ // "touch-only" step to collect a PIN if exactly one authenticator is
+ // discovered.
+ bool allow_skipping_pin_touch = false;
+
+ // android_client_data_ext is a compatibility hack to support the Clank
+ // caBLEv2 authenticator.
+ base::Optional<AndroidClientDataExtensionInput> android_client_data_ext;
};
MakeCredentialRequestHandler(
FidoDiscoveryFactory* fido_discovery_factory,
const base::flat_set<FidoTransportProtocol>& supported_transports,
CtapMakeCredentialRequest request_parameter,
- AuthenticatorSelectionCriteria authenticator_criteria,
const Options& options,
CompletionCallback completion_callback);
~MakeCredentialRequestHandler() override;
@@ -157,6 +181,11 @@ class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialRequestHandler
base::Optional<pin::TokenResponse> response);
void OnEnrollmentComplete(std::unique_ptr<CtapMakeCredentialRequest> request);
void OnEnrollmentDismissed();
+ void OnStartUvTokenOrFallback(
+ FidoAuthenticator* authenticator,
+ std::unique_ptr<CtapMakeCredentialRequest> request,
+ CtapDeviceResponseCode status,
+ base::Optional<pin::RetriesResponse> response);
void OnUvRetriesResponse(std::unique_ptr<CtapMakeCredentialRequest> request,
CtapDeviceResponseCode status,
base::Optional<pin::RetriesResponse> response);
@@ -176,7 +205,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) MakeCredentialRequestHandler
State state_ = State::kWaitingForTouch;
CtapMakeCredentialRequest request_;
base::Optional<base::RepeatingClosure> bio_enrollment_complete_barrier_;
- AuthenticatorSelectionCriteria authenticator_selection_criteria_;
const Options options_;
// authenticator_ points to the authenticator that will be used for this
diff --git a/chromium/device/fido/make_credential_task.cc b/chromium/device/fido/make_credential_task.cc
index 304f0018a18..ca6976033ce 100644
--- a/chromium/device/fido/make_credential_task.cc
+++ b/chromium/device/fido/make_credential_task.cc
@@ -51,6 +51,47 @@ bool CtapDeviceShouldUseU2fBecauseClientPinIsSet(
return client_pin_set && supports_u2f;
}
+// ConvertCTAPResponse returns the AuthenticatorMakeCredentialResponse for a
+// given CTAP response message in |cbor|. It wraps
+// ReadCTAPMakeCredentialResponse() and in addition fills in |is_resident_key|,
+// which requires looking at the request and device.
+base::Optional<AuthenticatorMakeCredentialResponse> ConvertCTAPResponse(
+ FidoDevice* device,
+ bool resident_key_required,
+ const base::Optional<cbor::Value>& cbor) {
+ DCHECK_EQ(device->supported_protocol(), ProtocolVersion::kCtap2);
+ DCHECK(device->device_info());
+
+ base::Optional<AuthenticatorMakeCredentialResponse> response =
+ ReadCTAPMakeCredentialResponse(device->DeviceTransport(), cbor);
+ if (!response) {
+ return base::nullopt;
+ }
+
+ // Fill in whether the created credential is client-side discoverable
+ // (resident). CTAP 2.0 authenticators may decide to treat all credentials as
+ // discoverable, so we need to omit the value unless a resident key was
+ // required.
+ DCHECK(!response->is_resident_key.has_value());
+ if (resident_key_required) {
+ response->is_resident_key = true;
+ } else {
+ const bool resident_key_supported =
+ device->device_info()->options.supports_resident_key;
+ const base::flat_set<Ctap2Version>& ctap2_versions =
+ device->device_info()->ctap2_versions;
+ DCHECK(!ctap2_versions.empty());
+ const bool is_at_least_ctap2_1 =
+ std::any_of(ctap2_versions.begin(), ctap2_versions.end(),
+ [](Ctap2Version v) { return v > Ctap2Version::kCtap2_0; });
+ if (!resident_key_supported || is_at_least_ctap2_1) {
+ response->is_resident_key = false;
+ }
+ }
+
+ return response;
+}
+
} // namespace
MakeCredentialTask::MakeCredentialTask(FidoDevice* device,
@@ -169,8 +210,8 @@ void MakeCredentialTask::MakeCredential() {
register_operation_ = std::make_unique<Ctap2DeviceOperation<
CtapMakeCredentialRequest, AuthenticatorMakeCredentialResponse>>(
device(), std::move(request), std::move(callback_),
- base::BindOnce(&ReadCTAPMakeCredentialResponse,
- device()->DeviceTransport()),
+ base::BindOnce(&ConvertCTAPResponse, device(),
+ request_.resident_key_required),
/*string_fixup_predicate=*/nullptr);
register_operation_->Start();
return;
@@ -210,8 +251,8 @@ void MakeCredentialTask::HandleResponseToSilentSignRequest(
register_operation_ = std::make_unique<Ctap2DeviceOperation<
CtapMakeCredentialRequest, AuthenticatorMakeCredentialResponse>>(
device(), std::move(request), std::move(callback_),
- base::BindOnce(&ReadCTAPMakeCredentialResponse,
- device()->DeviceTransport()),
+ base::BindOnce(&ConvertCTAPResponse, device(),
+ request_.resident_key_required),
/*string_fixup_predicate=*/nullptr);
register_operation_->Start();
return;
@@ -228,8 +269,8 @@ void MakeCredentialTask::HandleResponseToSilentSignRequest(
device(), GetTouchRequest(device()),
base::BindOnce(&MakeCredentialTask::HandleResponseToDummyTouch,
weak_factory_.GetWeakPtr()),
- base::BindOnce(&ReadCTAPMakeCredentialResponse,
- device()->DeviceTransport()),
+ base::BindOnce(&ConvertCTAPResponse, device(),
+ /*resident_key_required=*/false),
/*string_fixup_predicate=*/nullptr);
register_operation_->Start();
return;
@@ -267,8 +308,8 @@ void MakeCredentialTask::HandleResponseToSilentSignRequest(
register_operation_ = std::make_unique<Ctap2DeviceOperation<
CtapMakeCredentialRequest, AuthenticatorMakeCredentialResponse>>(
device(), std::move(request), std::move(callback_),
- base::BindOnce(&ReadCTAPMakeCredentialResponse,
- device()->DeviceTransport()),
+ base::BindOnce(&ConvertCTAPResponse, device(),
+ request_.resident_key_required),
/*string_fixup_predicate=*/nullptr);
register_operation_->Start();
}
@@ -306,6 +347,8 @@ void MakeCredentialTask::MaybeRevertU2fFallback(
device()->set_supported_protocol(ProtocolVersion::kCtap2);
}
+ DCHECK(!response || *response->is_resident_key == false);
+
std::move(callback_).Run(status, std::move(response));
}
diff --git a/chromium/device/fido/pin.h b/chromium/device/fido/pin.h
index ef91cb736d1..2732ef9de61 100644
--- a/chromium/device/fido/pin.h
+++ b/chromium/device/fido/pin.h
@@ -31,13 +31,20 @@ enum class Permissions : uint8_t {
kGetAssertion = 0x02,
kCredentialManagement = 0x04,
kBioEnrollment = 0x08,
- kPlatformConfiguration = 0x10,
+ kLargeBlobWrite = 0x10,
};
// kProtocolVersion is the version of the PIN protocol that this code
// implements.
constexpr int kProtocolVersion = 1;
+// Some commands that validate PinUvAuthTokens include this padding to ensure a
+// PinUvAuthParam cannot be reused across different commands.
+constexpr std::array<uint8_t, 32> kPinUvAuthTokenSafetyPadding = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
// IsValid returns true if |pin|, which must be UTF-8, is a syntactically valid
// PIN.
COMPONENT_EXPORT(DEVICE_FIDO) bool IsValid(const std::string& pin);
@@ -246,7 +253,7 @@ class HMACSecretRequest {
// decrypt a response, the shared key from the request is needed. Once a pin-
// token has been decrypted, it can be used to calculate the pinAuth parameters
// needed to show user-verification in future operations.
-class TokenResponse {
+class COMPONENT_EXPORT(DEVICE_FIDO) TokenResponse {
public:
~TokenResponse();
TokenResponse(const TokenResponse&);
diff --git a/chromium/device/fido/virtual_ctap2_device.cc b/chromium/device/fido/virtual_ctap2_device.cc
index f30470032b5..ca36525fc7a 100644
--- a/chromium/device/fido/virtual_ctap2_device.cc
+++ b/chromium/device/fido/virtual_ctap2_device.cc
@@ -24,12 +24,14 @@
#include "crypto/ec_private_key.h"
#include "device/fido/authenticator_get_assertion_response.h"
#include "device/fido/authenticator_make_credential_response.h"
+#include "device/fido/authenticator_supported_options.h"
#include "device/fido/bio/enrollment.h"
#include "device/fido/credential_management.h"
#include "device/fido/ctap_get_assertion_request.h"
#include "device/fido/ctap_make_credential_request.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h"
+#include "device/fido/large_blob.h"
#include "device/fido/opaque_attestation_statement.h"
#include "device/fido/p256_public_key.h"
#include "device/fido/pin.h"
@@ -59,7 +61,8 @@ 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);
+ static_cast<uint8_t>(pin::Permissions::kBioEnrollment) |
+ static_cast<uint8_t>(pin::Permissions::kLargeBlobWrite);
struct PinUvAuthTokenPermissions {
uint8_t permissions;
@@ -167,7 +170,8 @@ std::vector<uint8_t> ConstructMakeCredentialResponse(
base::span<const uint8_t> signature,
AuthenticatorData authenticator_data,
base::Optional<std::vector<uint8_t>> android_client_data_ext,
- bool enterprise_attestation_requested) {
+ bool enterprise_attestation_requested,
+ base::Optional<std::array<uint8_t, kLargeBlobKeyLength>> large_blob_key) {
cbor::Value::MapValue attestation_map;
attestation_map.emplace("alg", -7);
attestation_map.emplace("sig", fido_parsing_utils::Materialize(signature));
@@ -190,6 +194,9 @@ std::vector<uint8_t> ConstructMakeCredentialResponse(
}
make_credential_response.enterprise_attestation_returned =
enterprise_attestation_requested;
+ if (large_blob_key) {
+ make_credential_response.set_large_blob_key(*large_blob_key);
+ }
return AsCTAPStyleCBORBytes(make_credential_response);
}
@@ -417,6 +424,9 @@ std::vector<uint8_t> EncodeGetAssertionResponse(
response_map.emplace(0xf0,
cbor::Value(*response.android_client_data_ext()));
}
+ if (response.large_blob_key()) {
+ response_map.emplace(0x0b, cbor::Value(*response.large_blob_key()));
+ }
return WriteCBOR(cbor::Value(std::move(response_map)), allow_invalid_utf8);
}
@@ -439,12 +449,16 @@ VirtualCtap2Device::Config& VirtualCtap2Device::Config::operator=(
VirtualCtap2Device::Config::~Config() = default;
VirtualCtap2Device::VirtualCtap2Device() {
+ RegenerateKeyAgreementKey();
Init({ProtocolVersion::kCtap2});
}
VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state,
const Config& config)
: VirtualFidoDevice(std::move(state)), config_(config) {
+ RegenerateKeyAgreementKey();
+
+ Init({ProtocolVersion::kCtap2});
std::vector<ProtocolVersion> versions = {ProtocolVersion::kCtap2};
if (config.u2f_support) {
versions.emplace_back(ProtocolVersion::kU2f);
@@ -536,6 +550,12 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state,
options.enterprise_attestation = true;
}
+ if (config.large_blob_support) {
+ DCHECK(config.resident_key_support);
+ options_updated = true;
+ options.supports_large_blobs = true;
+ }
+
if (options_updated) {
device_info_->options = std::move(options);
}
@@ -554,6 +574,10 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state,
extensions.emplace_back(device::kExtensionAndroidClientData);
}
+ if (config.large_blob_support) {
+ extensions.emplace_back(device::kExtensionLargeBlobKey);
+ }
+
if (!extensions.empty()) {
device_info_->extensions.emplace(std::move(extensions));
}
@@ -575,6 +599,16 @@ VirtualCtap2Device::VirtualCtap2Device(scoped_refptr<State> state,
VirtualCtap2Device::~VirtualCtap2Device() = default;
+void VirtualCtap2Device::SetPin(std::string pin) {
+ DCHECK_NE(
+ device_info_->options.client_pin_availability,
+ AuthenticatorSupportedOptions::ClientPinAvailability::kNotSupported);
+ mutable_state()->pin = std::move(pin);
+ mutable_state()->pin_retries = device::kMaxPinRetries;
+ device_info_->options.client_pin_availability =
+ AuthenticatorSupportedOptions::ClientPinAvailability::kSupportedAndPinSet;
+}
+
// As all operations for VirtualCtap2Device are synchronous and we do not wait
// for user touch, Cancel command is no-op.
void VirtualCtap2Device::Cancel(CancelToken) {}
@@ -647,6 +681,9 @@ FidoDevice::CancelToken VirtualCtap2Device::DeviceTransact(
case CtapRequestCommand::kAuthenticatorBioEnrollmentPreview:
response_code = OnBioEnrollment(request_bytes, &response_data);
break;
+ case CtapRequestCommand::kAuthenticatorLargeBlobs:
+ response_code = OnLargeBlobs(request_bytes, &response_data);
+ break;
default:
break;
}
@@ -914,7 +951,7 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential(
// extensions but Chromium should not make this mistake.
DLOG(ERROR)
<< "Rejecting makeCredential due to unexpected hmac_secret extension";
- return base::nullopt;
+ return CtapDeviceResponseCode::kCtap2ErrUnsupportedExtension;
}
extensions_map.emplace(cbor::Value(kExtensionHmacSecret),
cbor::Value(true));
@@ -934,6 +971,19 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential(
cbor::Value(static_cast<int64_t>(cred_protect)));
}
+ if (request.large_blob_key) {
+ if (!config_.large_blob_support) {
+ DLOG(ERROR) << "Rejecting makeCredential due to unexpected largeBlobKey "
+ "extension";
+ return CtapDeviceResponseCode::kCtap2ErrUnsupportedExtension;
+ }
+ if (!request.resident_key_required) {
+ DLOG(ERROR)
+ << "largeBlobKey is not supported for non resident credentials";
+ return CtapDeviceResponseCode::kCtap2ErrInvalidOption;
+ }
+ }
+
if (config_.add_extra_extension) {
extensions_map.emplace(cbor::Value("unsolicited"), cbor::Value(42));
}
@@ -1025,9 +1075,16 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential(
client_data_json.size()));
}
+ base::Optional<std::array<uint8_t, kLargeBlobKeyLength>> large_blob_key;
+ if (request.large_blob_key) {
+ large_blob_key.emplace();
+ RAND_bytes(large_blob_key->data(), large_blob_key->size());
+ }
+
*response = ConstructMakeCredentialResponse(
std::move(attestation_cert), sig, std::move(authenticator_data),
- std::move(opt_android_client_data_ext), enterprise_attestation_requested);
+ std::move(opt_android_client_data_ext), enterprise_attestation_requested,
+ large_blob_key);
RegistrationData registration(std::move(private_key), rp_id_hash,
1 /* signature counter */);
@@ -1068,6 +1125,8 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential(
registration.hmac_key->second.size());
}
+ registration.large_blob_key = std::move(large_blob_key);
+
StoreNewKey(key_handle, std::move(registration));
return CtapDeviceResponseCode::kSuccess;
}
@@ -1350,6 +1409,15 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnGetAssertion(
assertion.SetUserEntity(registration.second->user.value());
}
+ if (request.large_blob_key) {
+ if (!config_.large_blob_support) {
+ return CtapDeviceResponseCode::kCtap2ErrUnsupportedExtension;
+ }
+ if (registration.second->large_blob_key) {
+ assertion.set_large_blob_key(*registration.second->large_blob_key);
+ }
+ }
+
if (opt_android_client_data_json) {
std::vector<uint8_t> android_client_data_ext;
fido_parsing_utils::Append(
@@ -1464,19 +1532,16 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand(
break;
case static_cast<int>(device::pin::Subcommand::kGetKeyAgreement): {
- bssl::UniquePtr<EC_KEY> key(
- EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
- CHECK(EC_KEY_generate_key(key.get()));
std::array<uint8_t, kP256X962Length> x962;
CHECK_EQ(x962.size(),
- EC_POINT_point2oct(EC_KEY_get0_group(key.get()),
- EC_KEY_get0_public_key(key.get()),
- POINT_CONVERSION_UNCOMPRESSED, x962.data(),
- x962.size(), nullptr /* BN_CTX */));
+ EC_POINT_point2oct(
+ EC_KEY_get0_group(mutable_state()->ecdh_key.get()),
+ EC_KEY_get0_public_key(mutable_state()->ecdh_key.get()),
+ POINT_CONVERSION_UNCOMPRESSED, x962.data(), x962.size(),
+ nullptr /* BN_CTX */));
response_map.emplace(static_cast<int>(pin::ResponseKey::kKeyAgreement),
pin::EncodeCOSEPublicKey(x962));
- mutable_state()->ecdh_key = std::move(key);
break;
}
@@ -1538,17 +1603,13 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand(
}
uint8_t shared_key[SHA256_DIGEST_LENGTH];
- if (!mutable_state()->ecdh_key) {
- // kGetKeyAgreement should have been called first.
- NOTREACHED();
- return CtapDeviceResponseCode::kCtap2ErrPinTokenExpired;
- }
pin::CalculateSharedKey(mutable_state()->ecdh_key.get(), peer_key->get(),
shared_key);
CtapDeviceResponseCode err =
ConfirmPresentedPIN(mutable_state(), shared_key, *encrypted_pin_hash);
if (err != CtapDeviceResponseCode::kSuccess) {
+ RegenerateKeyAgreementKey();
return err;
}
@@ -1616,6 +1677,7 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand(
CtapDeviceResponseCode err =
ConfirmPresentedPIN(mutable_state(), shared_key, *encrypted_pin_hash);
if (err != CtapDeviceResponseCode::kSuccess) {
+ RegenerateKeyAgreementKey();
return err;
}
@@ -1670,7 +1732,7 @@ base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnPINCommand(
return base::nullopt;
}
if (!config_.user_verification_succeeds) {
- return CtapDeviceResponseCode::kCtap2ErrPinInvalid;
+ return CtapDeviceResponseCode::kCtap2ErrUvInvalid;
}
mutable_state()->pin_retries = kMaxPinRetries;
@@ -2114,6 +2176,161 @@ CtapDeviceResponseCode VirtualCtap2Device::OnBioEnrollment(
return CtapDeviceResponseCode::kSuccess;
}
+CtapDeviceResponseCode VirtualCtap2Device::OnLargeBlobs(
+ base::span<const uint8_t> request_bytes,
+ std::vector<uint8_t>* response) {
+ if (!config_.large_blob_support) {
+ DLOG(ERROR) << "Large blob not supported";
+ return CtapDeviceResponseCode::kCtap2ErrUnsupportedExtension;
+ }
+
+ // Read request bytes into |cbor::Value::MapValue|.
+ const auto& cbor_request = cbor::Reader::Read(request_bytes);
+ if (!cbor_request || !cbor_request->is_map()) {
+ return CtapDeviceResponseCode::kCtap2ErrCBORUnexpectedType;
+ }
+ const auto& request_map = cbor_request->GetMap();
+
+ const auto offset_it = request_map.find(
+ cbor::Value(static_cast<uint8_t>(LargeBlobsRequestKey::kOffset)));
+ if (offset_it == request_map.end() || !offset_it->second.is_unsigned()) {
+ return CtapDeviceResponseCode::kCtap1ErrInvalidParameter;
+ }
+ const uint64_t offset = offset_it->second.GetUnsigned();
+
+ const auto get_it = request_map.find(
+ cbor::Value(static_cast<uint8_t>(LargeBlobsRequestKey::kGet)));
+ const auto set_it = request_map.find(
+ cbor::Value(static_cast<uint8_t>(LargeBlobsRequestKey::kSet)));
+ if ((get_it == request_map.end() && set_it == request_map.end()) ||
+ (get_it != request_map.end() && set_it != request_map.end())) {
+ return CtapDeviceResponseCode::kCtap1ErrInvalidParameter;
+ }
+ if ((get_it != request_map.end() && !get_it->second.is_unsigned()) ||
+ (set_it != request_map.end() && !set_it->second.is_bytestring())) {
+ return CtapDeviceResponseCode::kCtap1ErrInvalidParameter;
+ }
+ const auto length_it = request_map.find(
+ cbor::Value(static_cast<uint8_t>(LargeBlobsRequestKey::kLength)));
+ const size_t max_fragment_length = kLargeBlobDefaultMaxFragmentLength;
+
+ if (get_it != request_map.end()) {
+ if (length_it != request_map.end()) {
+ return CtapDeviceResponseCode::kCtap1ErrInvalidParameter;
+ }
+ const uint64_t get = get_it->second.GetUnsigned();
+ if (get > max_fragment_length) {
+ return CtapDeviceResponseCode::kCtap1ErrInvalidLength;
+ }
+ if (offset > mutable_state()->large_blob.size()) {
+ return CtapDeviceResponseCode::kCtap1ErrInvalidParameter;
+ }
+ cbor::Value::MapValue response_map;
+ response_map.emplace(
+ static_cast<uint8_t>(LargeBlobsResponseKey::kConfig),
+ base::make_span(
+ mutable_state()->large_blob.data() + offset,
+ std::min(get, mutable_state()->large_blob.size() - offset)));
+ *response =
+ cbor::Writer::Write(cbor::Value(std::move(response_map))).value();
+ } else {
+ DCHECK(set_it != request_map.end());
+ const std::vector<uint8_t>& set = set_it->second.GetBytestring();
+ if (set.size() > max_fragment_length) {
+ return CtapDeviceResponseCode::kCtap1ErrInvalidLength;
+ }
+ if (offset == 0) {
+ if (length_it == request_map.end() || !length_it->second.is_unsigned()) {
+ return CtapDeviceResponseCode::kCtap1ErrInvalidParameter;
+ }
+ const uint64_t length = length_it->second.GetUnsigned();
+ if (length > config_.available_large_blob_storage) {
+ return CtapDeviceResponseCode::kCtap2ErrLargeBlobStorageFull;
+ }
+ constexpr size_t kMinBlobLength = 17;
+ if (length < kMinBlobLength) {
+ return CtapDeviceResponseCode::kCtap1ErrInvalidParameter;
+ }
+ mutable_state()->large_blob_expected_length = length;
+ mutable_state()->large_blob_expected_next_offset = 0;
+ } else {
+ if (length_it != request_map.end()) {
+ return CtapDeviceResponseCode::kCtap1ErrInvalidParameter;
+ }
+ }
+
+ if (offset != mutable_state()->large_blob_expected_next_offset) {
+ return CtapDeviceResponseCode::kCtap1ErrInvalidSeq;
+ }
+
+ if (device_info_->options.client_pin_availability ==
+ AuthenticatorSupportedOptions::ClientPinAvailability::
+ kSupportedAndPinSet ||
+ device_info_->options.user_verification_availability ==
+ AuthenticatorSupportedOptions::UserVerificationAvailability::
+ kSupportedAndConfigured) {
+ // If the device is protected by some sort of user verification:
+ const auto pin_uv_auth_param_it = request_map.find(cbor::Value(
+ static_cast<uint8_t>(LargeBlobsRequestKey::kPinUvAuthParam)));
+ const auto pin_uv_auth_protocol_it = request_map.find(cbor::Value(
+ static_cast<uint8_t>(LargeBlobsRequestKey::kPinUvAuthProtocol)));
+ if (pin_uv_auth_param_it == request_map.end() ||
+ !pin_uv_auth_param_it->second.is_bytestring() ||
+ pin_uv_auth_protocol_it == request_map.end() ||
+ !pin_uv_auth_protocol_it->second.is_unsigned()) {
+ return CtapDeviceResponseCode::kCtap2ErrOperationDenied;
+ }
+ if (pin_uv_auth_protocol_it->second.GetUnsigned() !=
+ pin::kProtocolVersion) {
+ return CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid;
+ }
+ if (!(mutable_state()->pin_uv_token_permissions &
+ static_cast<uint8_t>(pin::Permissions::kLargeBlobWrite))) {
+ return CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid;
+ }
+
+ // verify(pinUvAuthToken,
+ // 32×0xff || h’0c00' || uint32LittleEndian(offset) ||
+ // contents of set byte string, i.e. not including an outer CBOR
+ // tag with major type two,
+ // pinUvAuthParam)
+ std::vector<uint8_t> pinauth_bytes;
+ pinauth_bytes.insert(pinauth_bytes.begin(),
+ pin::kPinUvAuthTokenSafetyPadding.begin(),
+ pin::kPinUvAuthTokenSafetyPadding.end());
+ pinauth_bytes.insert(pinauth_bytes.end(), kLargeBlobPinPrefix.begin(),
+ kLargeBlobPinPrefix.end());
+ auto offset_vec = fido_parsing_utils::Uint32LittleEndian(offset);
+ pinauth_bytes.insert(pinauth_bytes.end(), offset_vec.begin(),
+ offset_vec.end());
+ pinauth_bytes.insert(pinauth_bytes.end(), set.begin(), set.end());
+ if (!CheckPINToken(mutable_state()->pin_token,
+ pin_uv_auth_param_it->second.GetBytestring(),
+ pinauth_bytes)) {
+ return CtapDeviceResponseCode::kCtap2ErrPinAuthInvalid;
+ }
+ }
+ if (offset + set.size() > mutable_state()->large_blob_expected_length) {
+ return CtapDeviceResponseCode::kCtap1ErrInvalidParameter;
+ }
+ if (offset == 0) {
+ mutable_state()->large_blob_buffer.clear();
+ }
+ mutable_state()->large_blob_buffer.insert(
+ mutable_state()->large_blob_buffer.end(), set.begin(), set.end());
+ mutable_state()->large_blob_expected_next_offset =
+ mutable_state()->large_blob_buffer.size();
+ if (mutable_state()->large_blob_buffer.size() ==
+ mutable_state()->large_blob_expected_length) {
+ if (!VerifyLargeBlobArrayIntegrity(mutable_state()->large_blob_buffer)) {
+ return CtapDeviceResponseCode::kCtap2ErrIntegrityFailure;
+ }
+ mutable_state()->large_blob = mutable_state()->large_blob_buffer;
+ }
+ }
+ return CtapDeviceResponseCode::kSuccess;
+}
+
void VirtualCtap2Device::InitPendingRPs() {
mutable_state()->pending_rps.clear();
std::set<std::string> rp_ids;
@@ -2162,6 +2379,12 @@ void VirtualCtap2Device::InitPendingRegistrations(
}
}
+void VirtualCtap2Device::RegenerateKeyAgreementKey() {
+ bssl::UniquePtr<EC_KEY> key(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+ CHECK(EC_KEY_generate_key(key.get()));
+ mutable_state()->ecdh_key = std::move(key);
+}
+
void VirtualCtap2Device::GetNextRP(cbor::Value::MapValue* response_map) {
DCHECK(!mutable_state()->pending_rps.empty());
response_map->emplace(
diff --git a/chromium/device/fido/virtual_ctap2_device.h b/chromium/device/fido/virtual_ctap2_device.h
index 93535bddc36..6753add3f07 100644
--- a/chromium/device/fido/virtual_ctap2_device.h
+++ b/chromium/device/fido/virtual_ctap2_device.h
@@ -52,7 +52,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualCtap2Device
Config& operator=(const Config&);
~Config();
- base::flat_set<Ctap2Version> ctap2_versions = {Ctap2Version::kCtap2_0};
+ base::flat_set<Ctap2Version> ctap2_versions = {
+ std::begin(kCtap2Versions2_0), std::end(kCtap2Versions2_0)};
// u2f_support, if true, makes this device a dual-protocol (i.e. CTAP2 and
// U2F) device.
bool u2f_support = false;
@@ -68,6 +69,12 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualCtap2Device
uint8_t bio_enrollment_samples_required = 4;
bool cred_protect_support = false;
bool hmac_secret_support = false;
+ bool large_blob_support = false;
+ // The space available to store a large blob. In real authenticators this
+ // may change depending on the number of resident credentials. We treat this
+ // as a fixed size area for the large blob.
+ size_t available_large_blob_storage = 1024;
+
IncludeCredential include_credential_in_assertion_response =
IncludeCredential::ONLY_IF_NEEDED;
@@ -179,6 +186,9 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualCtap2Device
VirtualCtap2Device(scoped_refptr<State> state, const Config& config);
~VirtualCtap2Device() override;
+ // Configures and sets a PIN on the authenticator.
+ void SetPin(std::string pin);
+
// FidoDevice:
void Cancel(CancelToken) override;
CancelToken DeviceTransact(std::vector<uint8_t> command,
@@ -217,12 +227,15 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualCtap2Device
std::vector<uint8_t>* response);
CtapDeviceResponseCode OnBioEnrollment(base::span<const uint8_t> request,
std::vector<uint8_t>* response);
+ CtapDeviceResponseCode OnLargeBlobs(base::span<const uint8_t> request,
+ std::vector<uint8_t>* response);
CtapDeviceResponseCode OnAuthenticatorGetInfo(
std::vector<uint8_t>* response) const;
void InitPendingRPs();
void GetNextRP(cbor::Value::MapValue* response_map);
void InitPendingRegistrations(base::span<const uint8_t> rp_id_hash);
+ void RegenerateKeyAgreementKey();
AttestedCredentialData ConstructAttestedCredentialData(
base::span<const uint8_t> key_handle,
diff --git a/chromium/device/fido/virtual_fido_device.cc b/chromium/device/fido/virtual_fido_device.cc
index 418e1f5f27a..97ddd611e7c 100644
--- a/chromium/device/fido/virtual_fido_device.cc
+++ b/chromium/device/fido/virtual_fido_device.cc
@@ -93,7 +93,11 @@ class EVPBackedPrivateKey : public VirtualFidoDevice::PrivateKey {
ret.resize(EVP_PKEY_size(pkey_.get()));
size_t sig_len = ret.size();
- CHECK(EVP_DigestSignInit(md_ctx.get(), /*pctx=*/nullptr, EVP_sha256(),
+ // Ed25519 does not separate out the hash function as an independent
+ // variable so it must be nullptr in that case.
+ const EVP_MD* digest =
+ EVP_PKEY_id(pkey_.get()) == EVP_PKEY_ED25519 ? nullptr : EVP_sha256();
+ CHECK(EVP_DigestSignInit(md_ctx.get(), /*pctx=*/nullptr, digest,
/*engine=*/nullptr, pkey_.get()) &&
EVP_DigestSign(md_ctx.get(), ret.data(), &sig_len, msg.data(),
msg.size()) &&
@@ -427,7 +431,6 @@ bool VirtualFidoDevice::State::InjectResidentKey(
VirtualFidoDevice::VirtualFidoDevice() = default;
-
VirtualFidoDevice::VirtualFidoDevice(scoped_refptr<State> state)
: state_(std::move(state)) {}
diff --git a/chromium/device/fido/virtual_fido_device.h b/chromium/device/fido/virtual_fido_device.h
index a9bb4b39523..d3e8e95d58f 100644
--- a/chromium/device/fido/virtual_fido_device.h
+++ b/chromium/device/fido/virtual_fido_device.h
@@ -114,6 +114,8 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice {
base::Optional<std::pair<std::array<uint8_t, 32>, std::array<uint8_t, 32>>>
hmac_key;
+ base::Optional<std::array<uint8_t, 32>> large_blob_key;
+
DISALLOW_COPY_AND_ASSIGN(RegistrationData);
};
@@ -224,6 +226,17 @@ class COMPONENT_EXPORT(DEVICE_FIDO) VirtualFidoDevice : public FidoDevice {
// expected sequence of requests was sent.
std::vector<size_t> allow_list_sizes;
+ // The large-blob array. This is initialized to an empty CBOR array (0x80)
+ // followed by LEFT(SHA-256(h'80'), 16).
+ std::vector<uint8_t> large_blob = {0x80, 0x76, 0xbe, 0x8b, 0x52, 0x8d,
+ 0x00, 0x75, 0xf7, 0xaa, 0xe9, 0x8d,
+ 0x6f, 0xa5, 0x7a, 0x6d, 0x3c};
+ // Buffer that gets progressively filled with large blob fragments until
+ // committed.
+ std::vector<uint8_t> large_blob_buffer;
+ uint64_t large_blob_expected_next_offset = 0;
+ uint64_t large_blob_expected_length = 0;
+
FidoTransportProtocol transport =
FidoTransportProtocol::kUsbHumanInterfaceDevice;
diff --git a/chromium/device/fido/virtual_fido_device_factory.cc b/chromium/device/fido/virtual_fido_device_factory.cc
index aafc7858154..ffdf386c4b8 100644
--- a/chromium/device/fido/virtual_fido_device_factory.cc
+++ b/chromium/device/fido/virtual_fido_device_factory.cc
@@ -80,13 +80,13 @@ VirtualFidoDevice::State* VirtualFidoDeviceFactory::mutable_state() {
return state_.get();
}
-std::unique_ptr<FidoDiscoveryBase> VirtualFidoDeviceFactory::Create(
- FidoTransportProtocol transport) {
+std::vector<std::unique_ptr<FidoDiscoveryBase>>
+VirtualFidoDeviceFactory::Create(FidoTransportProtocol transport) {
if (transport != transport_) {
- return nullptr;
+ return {};
}
- return std::make_unique<VirtualFidoDeviceDiscovery>(
- transport_, state_, supported_protocol_, ctap2_config_);
+ return SingleDiscovery(std::make_unique<VirtualFidoDeviceDiscovery>(
+ transport_, state_, supported_protocol_, ctap2_config_));
}
bool VirtualFidoDeviceFactory::IsTestOverride() {
diff --git a/chromium/device/fido/virtual_fido_device_factory.h b/chromium/device/fido/virtual_fido_device_factory.h
index 92b9bf0a6e9..82958de2439 100644
--- a/chromium/device/fido/virtual_fido_device_factory.h
+++ b/chromium/device/fido/virtual_fido_device_factory.h
@@ -39,7 +39,7 @@ class VirtualFidoDeviceFactory : public device::FidoDiscoveryFactory {
protected:
// device::FidoDiscoveryFactory:
- std::unique_ptr<FidoDiscoveryBase> Create(
+ std::vector<std::unique_ptr<FidoDiscoveryBase>> Create(
FidoTransportProtocol transport) override;
bool IsTestOverride() override;
diff --git a/chromium/device/fido/win/logging.cc b/chromium/device/fido/win/logging.cc
index 8490cd0e286..f38110f6cca 100644
--- a/chromium/device/fido/win/logging.cc
+++ b/chromium/device/fido/win/logging.cc
@@ -32,6 +32,10 @@ std::wstring Quoted(base::WStringPiece in) {
return L"\"" + result + L"\"";
}
+std::wstring Quoted(const wchar_t* in) {
+ return Quoted(base::WStringPiece(in ? in : L""));
+}
+
} // namespace
std::ostream& operator<<(std::ostream& out,
diff --git a/chromium/device/fido/win/webauthn_api.cc b/chromium/device/fido/win/webauthn_api.cc
index c5ad6e58c06..b1483525ddd 100644
--- a/chromium/device/fido/win/webauthn_api.cc
+++ b/chromium/device/fido/win/webauthn_api.cc
@@ -14,6 +14,7 @@
#include "base/strings/string16.h"
#include "base/strings/string_piece_forward.h"
#include "base/strings/utf_string_conversions.h"
+#include "base/threading/scoped_thread_priority.h"
#include "components/device_event_log/device_event_log.h"
#include "device/fido/win/logging.h"
#include "device/fido/win/type_conversions.h"
@@ -34,8 +35,13 @@ constexpr uint32_t kWinWebAuthnTimeoutMilliseconds = 1000 * 60 * 5;
class WinWebAuthnApiImpl : public WinWebAuthnApi {
public:
WinWebAuthnApiImpl() : WinWebAuthnApi(), is_bound_(false) {
- webauthn_dll_ =
- LoadLibraryExA("webauthn.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
+ {
+ // Mitigate the issues caused by loading DLLs on a background thread
+ // (http://crbug/973868).
+ SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
+ webauthn_dll_ =
+ LoadLibraryExA("webauthn.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
+ }
if (!webauthn_dll_) {
return;
}
@@ -89,6 +95,9 @@ class WinWebAuthnApiImpl : public WinWebAuthnApi {
HRESULT IsUserVerifyingPlatformAuthenticatorAvailable(
BOOL* available) override {
DCHECK(is_bound_);
+ // Mitigate the issues caused by loading DLLs on a background thread
+ // (http://crbug/973868).
+ SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
return is_user_verifying_platform_authenticator_available_(available);
}
diff --git a/chromium/device/gamepad/BUILD.gn b/chromium/device/gamepad/BUILD.gn
index b0af36dfb64..352256fd712 100644
--- a/chromium/device/gamepad/BUILD.gn
+++ b/chromium/device/gamepad/BUILD.gn
@@ -9,6 +9,13 @@ if (is_android) {
import("//build/config/android/rules.gni") # For generate_jni().
}
+# This file depends on the legacy global sources assignment filter. It should
+# be converted to check target platform before assigning source files to the
+# sources variable. Remove this import and set_sources_assignment_filter call
+# when the file has been converted. See https://crbug.com/1018739 for details.
+import("//build/config/deprecated_default_sources_assignment_filter.gni")
+set_sources_assignment_filter(deprecated_default_sources_assignment_filter)
+
component("gamepad") {
output_name = "device_gamepad"
diff --git a/chromium/device/gamepad/android/java/src/org/chromium/device/gamepad/GamepadDevice.java b/chromium/device/gamepad/android/java/src/org/chromium/device/gamepad/GamepadDevice.java
deleted file mode 100644
index 267bd9601de..00000000000
--- a/chromium/device/gamepad/android/java/src/org/chromium/device/gamepad/GamepadDevice.java
+++ /dev/null
@@ -1,212 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.device.gamepad;
-
-import android.os.SystemClock;
-import android.view.InputDevice;
-import android.view.InputDevice.MotionRange;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-import androidx.annotation.VisibleForTesting;
-
-import java.util.Arrays;
-import java.util.BitSet;
-import java.util.List;
-
-/**
- * Manages information related to each connected gamepad device.
- */
-class GamepadDevice {
- // Axis ids are used as indices which are empirically always smaller than 256 so this allows
- // us to create cheap associative arrays.
- @VisibleForTesting
- static final int MAX_RAW_AXIS_VALUES = 256;
-
- // Keycodes are used as indices which are empirically always smaller than 256 so this allows
- // us to create cheap associative arrays.
- @VisibleForTesting
- static final int MAX_RAW_BUTTON_VALUES = 256;
-
- /** Keycodes which might be mapped by {@link GamepadMappings}. */
- private static final int RELEVANT_KEYCODES[] = {KeyEvent.KEYCODE_BUTTON_A,
- KeyEvent.KEYCODE_BUTTON_B, KeyEvent.KEYCODE_BUTTON_C, KeyEvent.KEYCODE_BUTTON_X,
- KeyEvent.KEYCODE_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_Z, KeyEvent.KEYCODE_BUTTON_L1,
- KeyEvent.KEYCODE_BUTTON_R1, KeyEvent.KEYCODE_BUTTON_L2, KeyEvent.KEYCODE_BUTTON_R2,
- KeyEvent.KEYCODE_BUTTON_SELECT, KeyEvent.KEYCODE_BUTTON_START,
- KeyEvent.KEYCODE_BUTTON_THUMBL, KeyEvent.KEYCODE_BUTTON_THUMBR,
- KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_LEFT,
- KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_BUTTON_MODE};
-
- // An id for the gamepad.
- private int mDeviceId;
- // The index of the gamepad in the Navigator.
- private int mDeviceIndex;
- // Last time the data for this gamepad was updated.
- private long mTimestamp;
-
- // Array of values for all axes of the gamepad.
- // All axis values must be linearly normalized to the range [-1.0 .. 1.0].
- // As appropriate, -1.0 should correspond to "up" or "left", and 1.0
- // should correspond to "down" or "right".
- private final float[] mAxisValues = new float[CanonicalAxisIndex.COUNT];
-
- private final float[] mButtonsValues = new float[CanonicalButtonIndex.COUNT];
-
- // When the user agent recognizes the attached inputDevice, it is recommended
- // that it be remapped to a canonical ordering when possible. Devices that are
- // not recognized should still be exposed in their raw form. Therefore we must
- // pass the raw Button and raw Axis values.
- private final float[] mRawButtons = new float[MAX_RAW_BUTTON_VALUES];
- private final float[] mRawAxes = new float[MAX_RAW_AXIS_VALUES];
-
- // An identification string for the gamepad.
- private String mDeviceName;
-
- // Array of axes ids.
- private int[] mAxes;
-
- // Mappings to canonical gamepad
- private GamepadMappings mMappings;
-
- GamepadDevice(int index, InputDevice inputDevice) {
- mDeviceIndex = index;
- mDeviceId = inputDevice.getId();
- mDeviceName = inputDevice.getName();
- mTimestamp = SystemClock.uptimeMillis();
- // Get axis ids and initialize axes values.
- final List<MotionRange> ranges = inputDevice.getMotionRanges();
- mAxes = new int[ranges.size()];
- int i = 0;
- for (MotionRange range : ranges) {
- if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
- int axis = range.getAxis();
- assert axis < MAX_RAW_AXIS_VALUES;
- mAxes[i++] = axis;
- }
- }
-
- // Get the set of relevant buttons which exist on the gamepad.
- BitSet buttons = new BitSet(KeyEvent.KEYCODE_BUTTON_MODE);
- boolean[] presentKeys = inputDevice.hasKeys(RELEVANT_KEYCODES);
- for (int j = 0; j < RELEVANT_KEYCODES.length; ++j) {
- if (presentKeys[j]) {
- buttons.set(RELEVANT_KEYCODES[j]);
- }
- }
-
- mMappings = GamepadMappings.getMappings(inputDevice, mAxes, buttons);
- }
-
- /**
- * Updates the axes and buttons maping of a gamepad device to a standard gamepad format.
- */
- public void updateButtonsAndAxesMapping() {
- mMappings.mapToStandardGamepad(mAxisValues, mButtonsValues, mRawAxes, mRawButtons);
- }
-
- /**
- * @return Device Id of the gamepad device.
- */
- public int getId() {
- return mDeviceId;
- }
-
- /**
- * @return Mapping status of the gamepad device.
- */
- public boolean isStandardGamepad() {
- return mMappings.isStandard();
- }
-
- /**
- * @return Device name of the gamepad device.
- */
- public String getName() {
- return mDeviceName;
- }
-
- /**
- * @return Device index of the gamepad device.
- */
- public int getIndex() {
- return mDeviceIndex;
- }
-
- /**
- * @return The timestamp when the gamepad device was last interacted.
- */
- public long getTimestamp() {
- return mTimestamp;
- }
-
- /**
- * @return The axes state of the gamepad device.
- */
- public float[] getAxes() {
- return mAxisValues;
- }
-
- /**
- * @return The buttons state of the gamepad device.
- */
- public float[] getButtons() {
- return mButtonsValues;
- }
-
- /**
- * @return The number of mapped buttons.
- */
- public int getButtonsLength() {
- return mMappings.getButtonsLength();
- }
-
- /**
- * Reset the axes and buttons data of the gamepad device every time gamepad data access is
- * paused.
- */
- public void clearData() {
- Arrays.fill(mAxisValues, 0);
- Arrays.fill(mRawAxes, 0);
- Arrays.fill(mButtonsValues, 0);
- Arrays.fill(mRawButtons, 0);
- }
-
- /**
- * Handles key event from the gamepad device.
- * @return True if the key event from the gamepad device has been consumed.
- */
- public boolean handleKeyEvent(KeyEvent event) {
- // Ignore event if it is not for standard gamepad key.
- if (!GamepadList.isGamepadEvent(event)) return false;
- int keyCode = event.getKeyCode();
- assert keyCode < MAX_RAW_BUTTON_VALUES;
- // Button value 0.0 must mean fully unpressed, and 1.0 must mean fully pressed.
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- mRawButtons[keyCode] = 1.0f;
- } else if (event.getAction() == KeyEvent.ACTION_UP) {
- mRawButtons[keyCode] = 0.0f;
- }
- mTimestamp = event.getEventTime();
-
- return true;
- }
-
- /**
- * Handles motion event from the gamepad device.
- * @return True if the motion event from the gamepad device has been consumed.
- */
- public boolean handleMotionEvent(MotionEvent event) {
- // Ignore event if it is not a standard gamepad motion event.
- if (!GamepadList.isGamepadEvent(event)) return false;
- // Update axes values.
- for (int i = 0; i < mAxes.length; i++) {
- int axis = mAxes[i];
- mRawAxes[axis] = event.getAxisValue(axis);
- }
- mTimestamp = event.getEventTime();
- return true;
- }
-}
diff --git a/chromium/device/gamepad/android/java/src/org/chromium/device/gamepad/GamepadList.java b/chromium/device/gamepad/android/java/src/org/chromium/device/gamepad/GamepadList.java
deleted file mode 100644
index 140c50adf2f..00000000000
--- a/chromium/device/gamepad/android/java/src/org/chromium/device/gamepad/GamepadList.java
+++ /dev/null
@@ -1,332 +0,0 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.device.gamepad;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.hardware.input.InputManager;
-import android.hardware.input.InputManager.InputDeviceListener;
-import android.view.InputDevice;
-import android.view.InputEvent;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-import org.chromium.base.ThreadUtils;
-import org.chromium.base.annotations.CalledByNative;
-import org.chromium.base.annotations.JNINamespace;
-import org.chromium.base.annotations.NativeMethods;
-
-/**
- * Class to manage connected gamepad devices list.
- *
- * It is a Java counterpart of GamepadPlatformDataFetcherAndroid and feeds Gamepad API with input
- * data.
- */
-@JNINamespace("device")
-public class GamepadList {
- private static final int MAX_GAMEPADS = 4;
-
- private final Object mLock = new Object();
-
- private final GamepadDevice[] mGamepadDevices = new GamepadDevice[MAX_GAMEPADS];
- private InputManager mInputManager;
- private int mAttachedToWindowCounter;
- private boolean mIsGamepadAPIActive;
- private InputDeviceListener mInputDeviceListener;
-
- private GamepadList() {
- mInputDeviceListener = new InputDeviceListener() {
- // Override InputDeviceListener methods
- @Override
- public void onInputDeviceChanged(int deviceId) {
- onInputDeviceChangedImpl(deviceId);
- }
-
- @Override
- public void onInputDeviceRemoved(int deviceId) {
- onInputDeviceRemovedImpl(deviceId);
- }
-
- @Override
- public void onInputDeviceAdded(int deviceId) {
- onInputDeviceAddedImpl(deviceId);
- }
- };
- }
-
- private void initializeDevices() {
- // Get list of all the attached input devices.
- int[] deviceIds = mInputManager.getInputDeviceIds();
- for (int i = 0; i < deviceIds.length; i++) {
- InputDevice inputDevice = InputDevice.getDevice(deviceIds[i]);
- // Check for gamepad device
- if (isGamepadDevice(inputDevice)) {
- // Register a new gamepad device.
- registerGamepad(inputDevice);
- }
- }
- }
-
- /**
- * Notifies the GamepadList that a {@link ContentView} is attached to a window and it should
- * prepare itself for gamepad input. It must be called before {@link onGenericMotionEvent} and
- * {@link dispatchKeyEvent}.
- */
- public static void onAttachedToWindow(Context context) {
- assert ThreadUtils.runningOnUiThread();
- getInstance().attachedToWindow(context);
- }
-
- private void attachedToWindow(Context context) {
- if (mAttachedToWindowCounter++ == 0) {
- mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
- synchronized (mLock) {
- initializeDevices();
- }
- // Register an input device listener.
- mInputManager.registerInputDeviceListener(mInputDeviceListener, null);
- }
- }
-
- /**
- * Notifies the GamepadList that a {@link ContentView} is detached from it's window.
- */
- @SuppressLint("MissingSuperCall")
- public static void onDetachedFromWindow() {
- assert ThreadUtils.runningOnUiThread();
- getInstance().detachedFromWindow();
- }
-
- private void detachedFromWindow() {
- if (--mAttachedToWindowCounter == 0) {
- synchronized (mLock) {
- for (int i = 0; i < MAX_GAMEPADS; ++i) {
- mGamepadDevices[i] = null;
- }
- }
- mInputManager.unregisterInputDeviceListener(mInputDeviceListener);
- mInputManager = null;
- }
- }
-
- // ------------------------------------------------------------
-
- private void onInputDeviceChangedImpl(int deviceId) {}
-
- private void onInputDeviceRemovedImpl(int deviceId) {
- synchronized (mLock) {
- unregisterGamepad(deviceId);
- }
- }
-
- private void onInputDeviceAddedImpl(int deviceId) {
- InputDevice inputDevice = InputDevice.getDevice(deviceId);
- if (!isGamepadDevice(inputDevice)) return;
- synchronized (mLock) {
- registerGamepad(inputDevice);
- }
- }
-
- // ------------------------------------------------------------
-
- private static GamepadList getInstance() {
- return LazyHolder.INSTANCE;
- }
-
- private int getDeviceCount() {
- int count = 0;
- for (int i = 0; i < MAX_GAMEPADS; i++) {
- if (getDevice(i) != null) {
- count++;
- }
- }
- return count;
- }
-
- private boolean isDeviceConnected(int index) {
- if (index < MAX_GAMEPADS && getDevice(index) != null) {
- return true;
- }
- return false;
- }
-
- private GamepadDevice getDeviceById(int deviceId) {
- for (int i = 0; i < MAX_GAMEPADS; i++) {
- GamepadDevice gamepad = mGamepadDevices[i];
- if (gamepad != null && gamepad.getId() == deviceId) {
- return gamepad;
- }
- }
- return null;
- }
-
- private GamepadDevice getDevice(int index) {
- // Maximum 4 Gamepads can be connected at a time starting at index zero.
- assert index >= 0 && index < MAX_GAMEPADS;
- return mGamepadDevices[index];
- }
-
- /**
- * Handles key events from the gamepad devices.
- * @return True if the event has been consumed.
- */
- public static boolean dispatchKeyEvent(KeyEvent event) {
- if (!isGamepadEvent(event)) return false;
- return getInstance().handleKeyEvent(event);
- }
-
- private boolean handleKeyEvent(KeyEvent event) {
- synchronized (mLock) {
- if (!mIsGamepadAPIActive) return false;
- GamepadDevice gamepad = getGamepadForEvent(event);
- if (gamepad == null) return false;
- return gamepad.handleKeyEvent(event);
- }
- }
-
- /**
- * Handles motion events from the gamepad devices.
- * @return True if the event has been consumed.
- */
- public static boolean onGenericMotionEvent(MotionEvent event) {
- if (!isGamepadEvent(event)) return false;
- return getInstance().handleMotionEvent(event);
- }
-
- private boolean handleMotionEvent(MotionEvent event) {
- synchronized (mLock) {
- if (!mIsGamepadAPIActive) return false;
- GamepadDevice gamepad = getGamepadForEvent(event);
- if (gamepad == null) return false;
- return gamepad.handleMotionEvent(event);
- }
- }
-
- private int getNextAvailableIndex() {
- // When multiple gamepads are connected to a user agent, indices must be assigned on a
- // first-come first-serve basis, starting at zero. If a gamepad is disconnected, previously
- // assigned indices must not be reassigned to gamepads that continue to be connected.
- // However, if a gamepad is disconnected, and subsequently the same or a different
- // gamepad is then connected, index entries must be reused.
-
- for (int i = 0; i < MAX_GAMEPADS; ++i) {
- if (getDevice(i) == null) {
- return i;
- }
- }
- // Reached maximum gamepads limit.
- return -1;
- }
-
- private boolean registerGamepad(InputDevice inputDevice) {
- int index = getNextAvailableIndex();
- if (index == -1) return false; // invalid index
-
- GamepadDevice gamepad = new GamepadDevice(index, inputDevice);
- mGamepadDevices[index] = gamepad;
- return true;
- }
-
- private void unregisterGamepad(int deviceId) {
- GamepadDevice gamepadDevice = getDeviceById(deviceId);
- if (gamepadDevice == null) return; // Not a registered device.
- int index = gamepadDevice.getIndex();
- mGamepadDevices[index] = null;
- }
-
- private static boolean isGamepadDevice(InputDevice inputDevice) {
- if (inputDevice == null) return false;
- return ((inputDevice.getSources() & InputDevice.SOURCE_JOYSTICK)
- == InputDevice.SOURCE_JOYSTICK);
- }
-
- private GamepadDevice getGamepadForEvent(InputEvent event) {
- return getDeviceById(event.getDeviceId());
- }
-
- /**
- * @return True if HTML5 gamepad API is active.
- */
- public static boolean isGamepadAPIActive() {
- return getInstance().mIsGamepadAPIActive;
- }
-
- /**
- * @return True if the motion event corresponds to a gamepad event.
- */
- public static boolean isGamepadEvent(MotionEvent event) {
- return ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK);
- }
-
- /**
- * @return True if event's keycode corresponds to a gamepad key.
- */
- public static boolean isGamepadEvent(KeyEvent event) {
- int keyCode = event.getKeyCode();
- switch (keyCode) {
- // Specific handling for dpad keys is required because
- // KeyEvent.isGamepadButton doesn't consider dpad keys.
- case KeyEvent.KEYCODE_DPAD_UP:
- case KeyEvent.KEYCODE_DPAD_DOWN:
- case KeyEvent.KEYCODE_DPAD_LEFT:
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- return true;
- default:
- return KeyEvent.isGamepadButton(keyCode);
- }
- }
-
- @CalledByNative
- static void updateGamepadData(long webGamepadsPtr) {
- getInstance().grabGamepadData(webGamepadsPtr);
- }
-
- private void grabGamepadData(long webGamepadsPtr) {
- synchronized (mLock) {
- for (int i = 0; i < MAX_GAMEPADS; i++) {
- final GamepadDevice device = getDevice(i);
- if (device != null) {
- device.updateButtonsAndAxesMapping();
- GamepadListJni.get().setGamepadData(GamepadList.this, webGamepadsPtr, i,
- device.isStandardGamepad(), true, device.getName(),
- device.getTimestamp(), device.getAxes(), device.getButtons(),
- device.getButtonsLength());
- } else {
- GamepadListJni.get().setGamepadData(GamepadList.this, webGamepadsPtr, i, false,
- false, null, 0, null, null, 0);
- }
- }
- }
- }
-
- @CalledByNative
- static void setGamepadAPIActive(boolean isActive) {
- getInstance().setIsGamepadActive(isActive);
- }
-
- private void setIsGamepadActive(boolean isGamepadActive) {
- synchronized (mLock) {
- mIsGamepadAPIActive = isGamepadActive;
- if (isGamepadActive) {
- for (int i = 0; i < MAX_GAMEPADS; i++) {
- GamepadDevice gamepadDevice = getDevice(i);
- if (gamepadDevice == null) continue;
- gamepadDevice.clearData();
- }
- }
- }
- }
-
- private static class LazyHolder {
- private static final GamepadList INSTANCE = new GamepadList();
- }
-
- @NativeMethods
- interface Natives {
- void setGamepadData(GamepadList caller, long webGamepadsPtr, int index, boolean mapping,
- boolean connected, String devicename, long timestamp, float[] axes, float[] buttons,
- int buttonsLength);
- }
-}
diff --git a/chromium/device/gamepad/android/java/src/org/chromium/device/gamepad/GamepadMappings.java b/chromium/device/gamepad/android/java/src/org/chromium/device/gamepad/GamepadMappings.java
deleted file mode 100644
index 291484598e8..00000000000
--- a/chromium/device/gamepad/android/java/src/org/chromium/device/gamepad/GamepadMappings.java
+++ /dev/null
@@ -1,642 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.device.gamepad;
-
-import android.os.Build;
-import android.view.InputDevice;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-import androidx.annotation.VisibleForTesting;
-
-import org.chromium.base.annotations.JNINamespace;
-
-import java.util.BitSet;
-
-/**
- * Class to manage mapping information related to each supported gamepad controller device.
- */
-@JNINamespace("content")
-abstract class GamepadMappings {
- @VisibleForTesting
- static final String NVIDIA_SHIELD_DEVICE_NAME_PREFIX = "NVIDIA Corporation NVIDIA Controller";
- @VisibleForTesting
- static final String MICROSOFT_XBOX_PAD_DEVICE_NAME = "Microsoft X-Box 360 pad";
- @VisibleForTesting
- static final String PS_DUALSHOCK_3_SIXAXIS_DEVICE_NAME = "Sony PLAYSTATION(R)3 Controller";
- @VisibleForTesting
- static final String SAMSUNG_EI_GP20_DEVICE_NAME = "Samsung Game Pad EI-GP20";
- @VisibleForTesting
- static final String AMAZON_FIRE_DEVICE_NAME = "Amazon Fire Game Controller";
-
- @VisibleForTesting
- static final int PS_DUALSHOCK_4_VENDOR_ID = 1356;
- @VisibleForTesting
- static final int PS_DUALSHOCK_4_PRODUCT_ID = 1476;
- @VisibleForTesting
- static final int PS_DUALSHOCK_4_SLIM_PRODUCT_ID = 2508;
- @VisibleForTesting
- static final int PS_DUALSHOCK_4_USB_RECEIVER_PRODUCT_ID = 2976;
-
- @VisibleForTesting
- static final int XBOX_ONE_S_2016_FIRMWARE_VENDOR_ID = 0x045e;
- @VisibleForTesting
- static final int XBOX_ONE_S_2016_FIRMWARE_PRODUCT_ID = 0x02e0;
-
- @VisibleForTesting
- static final int BROADCOM_VENDOR_ID = 0x0a5c;
- @VisibleForTesting
- static final int SNAKEBYTE_IDROIDCON_PRODUCT_ID = 0x8502;
-
- private static final float BUTTON_AXIS_DEADZONE = 0.01f;
-
- public static GamepadMappings getMappings(InputDevice device, int[] axes, BitSet buttons) {
- GamepadMappings mappings = getMappings(device.getProductId(), device.getVendorId(), axes);
- if (mappings == null) {
- mappings = getMappings(device.getName());
- }
- if (mappings == null) {
- mappings = new UnknownGamepadMappings(axes, buttons);
- }
- return mappings;
- }
-
- @VisibleForTesting
- static GamepadMappings getMappings(int productId, int vendorId, int[] axes) {
- // Device name of a DualShock 4 gamepad is "Wireless Controller". This is not reliably
- // unique so we better go by the product and vendor ids.
- if (vendorId == PS_DUALSHOCK_4_VENDOR_ID
- && (productId == PS_DUALSHOCK_4_PRODUCT_ID
- || productId == PS_DUALSHOCK_4_SLIM_PRODUCT_ID
- || productId == PS_DUALSHOCK_4_USB_RECEIVER_PRODUCT_ID)) {
- // Android 9 included improvements for Sony PlayStation gamepads that changed the
- // KeyEvent and MotionEvent codes for some buttons and axes. Use an alternate mapping
- // for versions of Android that include these improvements.
- if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- return new XboxCompatibleGamepadMappings();
- }
- return new Dualshock4GamepadMappingsPreP();
- }
- // Microsoft released a firmware update for the Xbox One S gamepad that modified the button
- // and axis assignments. With the new firmware, these gamepads work correctly in Android
- // using the default mapping, but a custom mapping is still required for the old firmware.
- // Both gamepads return the same device name, so we must compare hardware IDs to distinguish
- // them.
- if (vendorId == XBOX_ONE_S_2016_FIRMWARE_VENDOR_ID
- && productId == XBOX_ONE_S_2016_FIRMWARE_PRODUCT_ID) {
- return new XboxOneS2016FirmwareMappings();
- }
- if (vendorId == BROADCOM_VENDOR_ID && productId == SNAKEBYTE_IDROIDCON_PRODUCT_ID) {
- return new SnakebyteIDroidConMappings(axes);
- }
- return null;
- }
-
- @VisibleForTesting
- static GamepadMappings getMappings(String deviceName) {
- if (deviceName.startsWith(NVIDIA_SHIELD_DEVICE_NAME_PREFIX)
- || deviceName.equals(MICROSOFT_XBOX_PAD_DEVICE_NAME)) {
- return new XboxCompatibleGamepadMappings();
- } else if (deviceName.equals(PS_DUALSHOCK_3_SIXAXIS_DEVICE_NAME)) {
- // Android 9 included improvements for Sony PlayStation gamepads that changed the
- // KeyEvent and MotionEvent codes for some buttons and axes. Use an alternate mapping
- // for versions of Android that include these improvements.
- if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- return new Dualshock3SixAxisGamepadMappings();
- }
- return new Dualshock3SixAxisGamepadMappingsPreP();
- } else if (deviceName.equals(SAMSUNG_EI_GP20_DEVICE_NAME)) {
- return new SamsungEIGP20GamepadMappings();
- } else if (deviceName.equals(AMAZON_FIRE_DEVICE_NAME)) {
- return new AmazonFireGamepadMappings();
- }
- return null;
- }
-
- @VisibleForTesting
- static GamepadMappings getUnknownGamepadMappings(int[] axes, BitSet buttons) {
- return new UnknownGamepadMappings(axes, buttons);
- }
-
- /**
- * Method that specifies whether the mappings are standard or not.
- * It should be overridden in subclasses that don't provide standard
- * mappings.
- */
- public boolean isStandard() {
- return true;
- }
-
- /**
- * Returns the number of mapped buttons. Subclasses which support more or fewer buttons (e.g. no
- * meta button) should override this.
- */
- public int getButtonsLength() {
- return CanonicalButtonIndex.COUNT;
- }
-
- /**
- * Method implemented by subclasses to perform mapping from raw axes and buttons
- * to canonical axes and buttons.
- */
- public abstract void mapToStandardGamepad(float[] mappedAxes, float[] mappedButtons,
- float[] rawAxes, float[] rawButtons);
-
- private static void mapCommonXYABButtons(float[] mappedButtons, float[] rawButtons) {
- float a = rawButtons[KeyEvent.KEYCODE_BUTTON_A];
- float b = rawButtons[KeyEvent.KEYCODE_BUTTON_B];
- float x = rawButtons[KeyEvent.KEYCODE_BUTTON_X];
- float y = rawButtons[KeyEvent.KEYCODE_BUTTON_Y];
- mappedButtons[CanonicalButtonIndex.PRIMARY] = a;
- mappedButtons[CanonicalButtonIndex.SECONDARY] = b;
- mappedButtons[CanonicalButtonIndex.TERTIARY] = x;
- mappedButtons[CanonicalButtonIndex.QUATERNARY] = y;
- }
-
- private static void mapCommonStartSelectMetaButtons(float[] mappedButtons, float[] rawButtons) {
- float start = rawButtons[KeyEvent.KEYCODE_BUTTON_START];
- float select = rawButtons[KeyEvent.KEYCODE_BUTTON_SELECT];
- float mode = rawButtons[KeyEvent.KEYCODE_BUTTON_MODE];
- mappedButtons[CanonicalButtonIndex.START] = start;
- mappedButtons[CanonicalButtonIndex.BACK_SELECT] = select;
- mappedButtons[CanonicalButtonIndex.META] = mode;
- }
-
- private static void mapCommonThumbstickButtons(float[] mappedButtons, float[] rawButtons) {
- float thumbL = rawButtons[KeyEvent.KEYCODE_BUTTON_THUMBL];
- float thumbR = rawButtons[KeyEvent.KEYCODE_BUTTON_THUMBR];
- mappedButtons[CanonicalButtonIndex.LEFT_THUMBSTICK] = thumbL;
- mappedButtons[CanonicalButtonIndex.RIGHT_THUMBSTICK] = thumbR;
- }
-
- /**
- * Method for mapping the L1/R1 buttons to lower shoulder buttons, rather than
- * upper shoulder as the user would normally expect. Please think twice before
- * using this, as it can easily confuse the user. It is only really useful if
- * the controller completely lacks a second set of shoulder buttons.
- */
- private static void mapUpperTriggerButtonsToBottomShoulder(float[] mappedButtons,
- float[] rawButtons) {
- float l1 = rawButtons[KeyEvent.KEYCODE_BUTTON_L1];
- float r1 = rawButtons[KeyEvent.KEYCODE_BUTTON_R1];
- mappedButtons[CanonicalButtonIndex.LEFT_TRIGGER] = l1;
- mappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER] = r1;
- }
-
- private static void mapTriggerButtonsToTopShoulder(float[] mappedButtons, float[] rawButtons) {
- float l1 = rawButtons[KeyEvent.KEYCODE_BUTTON_L1];
- float r1 = rawButtons[KeyEvent.KEYCODE_BUTTON_R1];
- mappedButtons[CanonicalButtonIndex.LEFT_SHOULDER] = l1;
- mappedButtons[CanonicalButtonIndex.RIGHT_SHOULDER] = r1;
- }
-
- private static void mapCommonDpadButtons(float[] mappedButtons, float[] rawButtons) {
- float dpadDown = rawButtons[KeyEvent.KEYCODE_DPAD_DOWN];
- float dpadUp = rawButtons[KeyEvent.KEYCODE_DPAD_UP];
- float dpadLeft = rawButtons[KeyEvent.KEYCODE_DPAD_LEFT];
- float dpadRight = rawButtons[KeyEvent.KEYCODE_DPAD_RIGHT];
- mappedButtons[CanonicalButtonIndex.DPAD_DOWN] = dpadDown;
- mappedButtons[CanonicalButtonIndex.DPAD_UP] = dpadUp;
- mappedButtons[CanonicalButtonIndex.DPAD_LEFT] = dpadLeft;
- mappedButtons[CanonicalButtonIndex.DPAD_RIGHT] = dpadRight;
- }
-
- private static void mapXYAxes(float[] mappedAxes, float[] rawAxes) {
- mappedAxes[CanonicalAxisIndex.LEFT_STICK_X] = rawAxes[MotionEvent.AXIS_X];
- mappedAxes[CanonicalAxisIndex.LEFT_STICK_Y] = rawAxes[MotionEvent.AXIS_Y];
- }
-
- private static void mapRXAndRYAxesToRightStick(float[] mappedAxes, float[] rawAxes) {
- mappedAxes[CanonicalAxisIndex.RIGHT_STICK_X] = rawAxes[MotionEvent.AXIS_RX];
- mappedAxes[CanonicalAxisIndex.RIGHT_STICK_Y] = rawAxes[MotionEvent.AXIS_RY];
- }
-
- private static void mapZAndRZAxesToRightStick(float[] mappedAxes, float[] rawAxes) {
- mappedAxes[CanonicalAxisIndex.RIGHT_STICK_X] = rawAxes[MotionEvent.AXIS_Z];
- mappedAxes[CanonicalAxisIndex.RIGHT_STICK_Y] = rawAxes[MotionEvent.AXIS_RZ];
- }
-
- private static void mapPedalAxesToBottomShoulder(float[] mappedButtons, float[] rawAxes) {
- float lTrigger = rawAxes[MotionEvent.AXIS_BRAKE];
- float rTrigger = rawAxes[MotionEvent.AXIS_GAS];
- mappedButtons[CanonicalButtonIndex.LEFT_TRIGGER] = lTrigger;
- mappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER] = rTrigger;
- }
-
- private static void mapTriggerAxesToBottomShoulder(float[] mappedButtons, float[] rawAxes) {
- float lTrigger = rawAxes[MotionEvent.AXIS_LTRIGGER];
- float rTrigger = rawAxes[MotionEvent.AXIS_RTRIGGER];
- mappedButtons[CanonicalButtonIndex.LEFT_TRIGGER] = lTrigger;
- mappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER] = rTrigger;
- }
-
- private static void mapZAxisToBottomShoulder(float[] mappedButtons, float[] rawAxes) {
- float z = rawAxes[MotionEvent.AXIS_Z];
- mappedButtons[CanonicalButtonIndex.LEFT_TRIGGER] = z > BUTTON_AXIS_DEADZONE ? z : 0.0f;
- mappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER] = -z > BUTTON_AXIS_DEADZONE ? -z : 0.0f;
- }
-
- private static void mapLowerTriggerButtonsToBottomShoulder(float[] mappedButtons,
- float[] rawButtons) {
- float l2 = rawButtons[KeyEvent.KEYCODE_BUTTON_L2];
- float r2 = rawButtons[KeyEvent.KEYCODE_BUTTON_R2];
- mappedButtons[CanonicalButtonIndex.LEFT_TRIGGER] = l2;
- mappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER] = r2;
- }
-
- @VisibleForTesting
- static float negativeAxisValueAsButton(float input) {
- return (input < -0.5f) ? 1.f : 0.f;
- }
-
- @VisibleForTesting
- static float positiveAxisValueAsButton(float input) {
- return (input > 0.5f) ? 1.f : 0.f;
- }
-
- private static void mapHatAxisToDpadButtons(float[] mappedButtons, float[] rawAxes) {
- float hatX = rawAxes[MotionEvent.AXIS_HAT_X];
- float hatY = rawAxes[MotionEvent.AXIS_HAT_Y];
- mappedButtons[CanonicalButtonIndex.DPAD_LEFT] = negativeAxisValueAsButton(hatX);
- mappedButtons[CanonicalButtonIndex.DPAD_RIGHT] = positiveAxisValueAsButton(hatX);
- mappedButtons[CanonicalButtonIndex.DPAD_UP] = negativeAxisValueAsButton(hatY);
- mappedButtons[CanonicalButtonIndex.DPAD_DOWN] = positiveAxisValueAsButton(hatY);
- }
-
- private static class AmazonFireGamepadMappings extends GamepadMappings {
-
- /**
- * Method for mapping Amazon Fire gamepad axis and button values
- * to standard gamepad button and axes values.
- */
- @Override
- public void mapToStandardGamepad(float[] mappedAxes, float[] mappedButtons,
- float[] rawAxes, float[] rawButtons) {
- mapCommonXYABButtons(mappedButtons, rawButtons);
- mapTriggerButtonsToTopShoulder(mappedButtons, rawButtons);
- mapCommonThumbstickButtons(mappedButtons, rawButtons);
- mapCommonStartSelectMetaButtons(mappedButtons, rawButtons);
- mapPedalAxesToBottomShoulder(mappedButtons, rawAxes);
- mapHatAxisToDpadButtons(mappedButtons, rawAxes);
-
- mapXYAxes(mappedAxes, rawAxes);
- mapZAndRZAxesToRightStick(mappedAxes, rawAxes);
- }
- }
-
- private static class XboxCompatibleGamepadMappings extends GamepadMappings {
-
- /**
- * Method for mapping Xbox 360-compatible gamepad axis and button values
- * to standard gamepad button and axes values.
- */
- @Override
- public void mapToStandardGamepad(float[] mappedAxes, float[] mappedButtons,
- float[] rawAxes, float[] rawButtons) {
- mapCommonXYABButtons(mappedButtons, rawButtons);
- mapTriggerButtonsToTopShoulder(mappedButtons, rawButtons);
- mapCommonThumbstickButtons(mappedButtons, rawButtons);
- mapCommonStartSelectMetaButtons(mappedButtons, rawButtons);
- mapTriggerAxesToBottomShoulder(mappedButtons, rawAxes);
- mapHatAxisToDpadButtons(mappedButtons, rawAxes);
-
- mapXYAxes(mappedAxes, rawAxes);
- mapZAndRZAxesToRightStick(mappedAxes, rawAxes);
- }
- }
-
- private static class SnakebyteIDroidConMappings extends GamepadMappings {
- private final boolean mAnalogMode;
-
- public SnakebyteIDroidConMappings(int[] axes) {
- // Digital mode has X, Y, Z, RZ, HAT_X, HAT_Y
- // Analog mode has X, Y, Z, RX, RY, HAT_X, HAT_Y
- mAnalogMode = arrayContains(axes, MotionEvent.AXIS_RX);
- }
-
- private static boolean arrayContains(int[] array, int element) {
- for (int e : array) {
- if (e == element) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public int getButtonsLength() {
- // No meta button.
- return CanonicalButtonIndex.COUNT - 1;
- }
-
- @Override
- public void mapToStandardGamepad(
- float[] mappedAxes, float[] mappedButtons, float[] rawAxes, float[] rawButtons) {
- mapCommonXYABButtons(mappedButtons, rawButtons);
- mapTriggerButtonsToTopShoulder(mappedButtons, rawButtons);
- mapCommonStartSelectMetaButtons(mappedButtons, rawButtons);
- mapXYAxes(mappedAxes, rawAxes);
- mapHatAxisToDpadButtons(mappedButtons, rawAxes);
-
- // On older versions of Android the thumbstick buttons are incorrectly mapped to C and
- // Z. Support either.
- float thumbL = rawButtons[KeyEvent.KEYCODE_BUTTON_THUMBL];
- float thumbR = rawButtons[KeyEvent.KEYCODE_BUTTON_THUMBR];
- float c = rawButtons[KeyEvent.KEYCODE_BUTTON_C];
- float z = rawButtons[KeyEvent.KEYCODE_BUTTON_Z];
- mappedButtons[CanonicalButtonIndex.LEFT_THUMBSTICK] = Math.max(thumbL, c);
- mappedButtons[CanonicalButtonIndex.RIGHT_THUMBSTICK] = Math.max(thumbR, z);
-
- if (mAnalogMode) {
- mapZAxisToBottomShoulder(mappedButtons, rawAxes);
- mapRXAndRYAxesToRightStick(mappedAxes, rawAxes);
- } else {
- mapLowerTriggerButtonsToBottomShoulder(mappedButtons, rawButtons);
- mapZAndRZAxesToRightStick(mappedAxes, rawAxes);
- }
- }
- }
-
- private static class XboxOneS2016FirmwareMappings extends GamepadMappings {
- private boolean mLeftTriggerActivated;
- private boolean mRightTriggerActivated;
-
- /**
- * Method for mapping Xbox One S controller (in Bluetooth mode) to
- * standard gamepad button and axes values.
- */
- @Override
- public void mapToStandardGamepad(
- float[] mappedAxes, float[] mappedButtons, float[] rawAxes, float[] rawButtons) {
- mappedButtons[CanonicalButtonIndex.PRIMARY] = rawButtons[KeyEvent.KEYCODE_BUTTON_A];
- mappedButtons[CanonicalButtonIndex.SECONDARY] = rawButtons[KeyEvent.KEYCODE_BUTTON_B];
- mappedButtons[CanonicalButtonIndex.TERTIARY] = rawButtons[KeyEvent.KEYCODE_BUTTON_C];
- mappedButtons[CanonicalButtonIndex.QUATERNARY] = rawButtons[KeyEvent.KEYCODE_BUTTON_X];
-
- mappedButtons[CanonicalButtonIndex.LEFT_SHOULDER] =
- rawButtons[KeyEvent.KEYCODE_BUTTON_Y];
- mappedButtons[CanonicalButtonIndex.RIGHT_SHOULDER] =
- rawButtons[KeyEvent.KEYCODE_BUTTON_Z];
-
- mappedButtons[CanonicalButtonIndex.BACK_SELECT] =
- rawButtons[KeyEvent.KEYCODE_BUTTON_L1];
- mappedButtons[CanonicalButtonIndex.START] = rawButtons[KeyEvent.KEYCODE_BUTTON_R1];
-
- mappedButtons[CanonicalButtonIndex.LEFT_THUMBSTICK] =
- rawButtons[KeyEvent.KEYCODE_BUTTON_L2];
- mappedButtons[CanonicalButtonIndex.RIGHT_THUMBSTICK] =
- rawButtons[KeyEvent.KEYCODE_BUTTON_R2];
-
- // The left and right triggers on the Xbox One S controller
- // are exposed as AXIS_Z and AXIS_RZ respectively. However,
- // these nominally idle at -1 rather than 0, like other triggers.
- // Unfortunately, the -1 value is only reported upon the first
- // activation of each trigger axis. In order to prevent idling at
- // 0.5 before trigger activation, we only expose trigger values
- // when we've seen them report a non-zero value at least once.
- if (rawAxes[MotionEvent.AXIS_Z] != 0) {
- mLeftTriggerActivated = true;
- }
- if (rawAxes[MotionEvent.AXIS_RZ] != 0) {
- mRightTriggerActivated = true;
- }
- if (mLeftTriggerActivated) {
- mappedButtons[CanonicalButtonIndex.LEFT_TRIGGER] =
- (rawAxes[MotionEvent.AXIS_Z] + 1) / 2;
- } else {
- mappedButtons[CanonicalButtonIndex.LEFT_TRIGGER] = 0.f;
- }
- if (mRightTriggerActivated) {
- mappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER] =
- (rawAxes[MotionEvent.AXIS_RZ] + 1) / 2;
- } else {
- mappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER] = 0.f;
- }
-
- mapHatAxisToDpadButtons(mappedButtons, rawAxes);
- mapXYAxes(mappedAxes, rawAxes);
- mapRXAndRYAxesToRightStick(mappedAxes, rawAxes);
- }
-
- @Override
- public int getButtonsLength() {
- // No meta button.
- return CanonicalButtonIndex.COUNT - 1;
- }
- }
-
- private static class Dualshock3SixAxisGamepadMappingsPreP extends GamepadMappings {
- /**
- * Method for mapping DualShock 3 and SIXAXIS gamepad inputs to standard gamepad button and
- * axis values. This mapping function should only be used on Android 8 and earlier.
- */
- @Override
- public void mapToStandardGamepad(float[] mappedAxes, float[] mappedButtons,
- float[] rawAxes, float[] rawButtons) {
- // On DualShock 3 and SIXAXIS, X/Y has higher priority.
- float a = rawButtons[KeyEvent.KEYCODE_BUTTON_A];
- float b = rawButtons[KeyEvent.KEYCODE_BUTTON_B];
- float x = rawButtons[KeyEvent.KEYCODE_BUTTON_X];
- float y = rawButtons[KeyEvent.KEYCODE_BUTTON_Y];
- mappedButtons[CanonicalButtonIndex.PRIMARY] = x;
- mappedButtons[CanonicalButtonIndex.SECONDARY] = y;
- mappedButtons[CanonicalButtonIndex.TERTIARY] = a;
- mappedButtons[CanonicalButtonIndex.QUATERNARY] = b;
-
- mapTriggerButtonsToTopShoulder(mappedButtons, rawButtons);
- mapCommonThumbstickButtons(mappedButtons, rawButtons);
- mapCommonDpadButtons(mappedButtons, rawButtons);
- mapCommonStartSelectMetaButtons(mappedButtons, rawButtons);
- mapTriggerAxesToBottomShoulder(mappedButtons, rawAxes);
-
- mapXYAxes(mappedAxes, rawAxes);
- mapZAndRZAxesToRightStick(mappedAxes, rawAxes);
- }
- }
-
- private static class Dualshock3SixAxisGamepadMappings extends GamepadMappings {
- /**
- * Method for mapping DualShock 3 and SIXAXIS gamepad inputs to standard gamepad button and
- * axis values. This mapping function should only be used on Android 10+.
- */
- @Override
- public void mapToStandardGamepad(
- float[] mappedAxes, float[] mappedButtons, float[] rawAxes, float[] rawButtons) {
- mapCommonXYABButtons(mappedButtons, rawButtons);
- mapTriggerButtonsToTopShoulder(mappedButtons, rawButtons);
- mapCommonThumbstickButtons(mappedButtons, rawButtons);
- mapCommonStartSelectMetaButtons(mappedButtons, rawButtons);
- mapCommonDpadButtons(mappedButtons, rawButtons);
- mapXYAxes(mappedAxes, rawAxes);
- mapZAndRZAxesToRightStick(mappedAxes, rawAxes);
- mapTriggerAxesToBottomShoulder(mappedButtons, rawAxes);
- }
- }
-
- static class Dualshock4GamepadMappingsPreP extends GamepadMappings {
- // Scale input from [-1, 1] to [0, 1] uniformly.
- private static float scaleRxRy(float input) {
- return 1.f - ((1.f - input) / 2.f);
- }
-
- /**
- * Method for mapping DualShock 4 gamepad inputs to standard gamepad button and axis values.
- * This mapping function should only be used on Android 9 and earlier.
- */
- @Override
- public void mapToStandardGamepad(
- float[] mappedAxes, float[] mappedButtons, float[] rawAxes, float[] rawButtons) {
- float a = rawButtons[KeyEvent.KEYCODE_BUTTON_A];
- float b = rawButtons[KeyEvent.KEYCODE_BUTTON_B];
- float c = rawButtons[KeyEvent.KEYCODE_BUTTON_C];
- float x = rawButtons[KeyEvent.KEYCODE_BUTTON_X];
- mappedButtons[CanonicalButtonIndex.PRIMARY] = b;
- mappedButtons[CanonicalButtonIndex.SECONDARY] = c;
- mappedButtons[CanonicalButtonIndex.TERTIARY] = a;
- mappedButtons[CanonicalButtonIndex.QUATERNARY] = x;
-
- float y = rawButtons[KeyEvent.KEYCODE_BUTTON_Y];
- float z = rawButtons[KeyEvent.KEYCODE_BUTTON_Z];
- mappedButtons[CanonicalButtonIndex.LEFT_SHOULDER] = y;
- mappedButtons[CanonicalButtonIndex.RIGHT_SHOULDER] = z;
-
- float rx = rawAxes[MotionEvent.AXIS_RX];
- float ry = rawAxes[MotionEvent.AXIS_RY];
- mappedButtons[CanonicalButtonIndex.LEFT_TRIGGER] = scaleRxRy(rx);
- mappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER] = scaleRxRy(ry);
-
- float share = rawButtons[KeyEvent.KEYCODE_BUTTON_L2];
- float options = rawButtons[KeyEvent.KEYCODE_BUTTON_R2];
- mappedButtons[CanonicalButtonIndex.BACK_SELECT] = share;
- mappedButtons[CanonicalButtonIndex.START] = options;
-
- float thumbL = rawButtons[KeyEvent.KEYCODE_BUTTON_SELECT];
- float thumbR = rawButtons[KeyEvent.KEYCODE_BUTTON_START];
- mappedButtons[CanonicalButtonIndex.LEFT_THUMBSTICK] = thumbL;
- mappedButtons[CanonicalButtonIndex.RIGHT_THUMBSTICK] = thumbR;
-
- float mode = rawButtons[KeyEvent.KEYCODE_BUTTON_MODE];
- mappedButtons[CanonicalButtonIndex.META] = mode;
-
- mapHatAxisToDpadButtons(mappedButtons, rawAxes);
- mapXYAxes(mappedAxes, rawAxes);
- mapZAndRZAxesToRightStick(mappedAxes, rawAxes);
- }
- }
-
- private static class SamsungEIGP20GamepadMappings extends GamepadMappings {
- /**
- * Method for mapping Samsung GamePad EI-GP20 axis and button values
- * to standard gamepad button and axes values.
- */
- @Override
- public void mapToStandardGamepad(float[] mappedAxes, float[] mappedButtons,
- float[] rawAxes, float[] rawButtons) {
- mapCommonXYABButtons(mappedButtons, rawButtons);
- mapUpperTriggerButtonsToBottomShoulder(mappedButtons, rawButtons);
- mapCommonThumbstickButtons(mappedButtons, rawButtons);
- mapCommonStartSelectMetaButtons(mappedButtons, rawButtons);
- mapHatAxisToDpadButtons(mappedButtons, rawAxes);
-
- mapXYAxes(mappedAxes, rawAxes);
- mapRXAndRYAxesToRightStick(mappedAxes, rawAxes);
- }
- }
-
- private static class UnknownGamepadMappings extends GamepadMappings {
- private int mLeftTriggerAxis = -1;
- private int mRightTriggerAxis = -1;
- private int mRightStickXAxis = -1;
- private int mRightStickYAxis = -1;
- private boolean mUseHatAxes;
- private final boolean mHasMetaButton;
-
- UnknownGamepadMappings(int[] axes, BitSet buttons) {
- mHasMetaButton = buttons.get(KeyEvent.KEYCODE_BUTTON_MODE);
-
- int hatAxesFound = 0;
-
- for (int axis : axes) {
- switch (axis) {
- case MotionEvent.AXIS_LTRIGGER:
- case MotionEvent.AXIS_BRAKE:
- mLeftTriggerAxis = axis;
- break;
- case MotionEvent.AXIS_RTRIGGER:
- case MotionEvent.AXIS_GAS:
- case MotionEvent.AXIS_THROTTLE:
- mRightTriggerAxis = axis;
- break;
- case MotionEvent.AXIS_RX:
- case MotionEvent.AXIS_Z:
- mRightStickXAxis = axis;
- break;
- case MotionEvent.AXIS_RY:
- case MotionEvent.AXIS_RZ:
- mRightStickYAxis = axis;
- break;
- case MotionEvent.AXIS_HAT_X:
- hatAxesFound++;
- break;
- case MotionEvent.AXIS_HAT_Y:
- hatAxesFound++;
- break;
- default:
- break;
- }
- }
-
- if (hatAxesFound == 2) {
- mUseHatAxes = true;
- }
- }
-
- @Override
- public boolean isStandard() {
- // These mappings should not be considered standard
- return false;
- }
-
- @Override
- public int getButtonsLength() {
- return mHasMetaButton ? CanonicalButtonIndex.COUNT : CanonicalButtonIndex.COUNT - 1;
- }
-
- @Override
- public void mapToStandardGamepad(float[] mappedAxes, float[] mappedButtons,
- float[] rawAxes, float[] rawButtons) {
- // These are shared among all gamepads intended for use with Android
- // that we tested so far.
- mapCommonXYABButtons(mappedButtons, rawButtons);
- mapTriggerButtonsToTopShoulder(mappedButtons, rawButtons);
- mapCommonThumbstickButtons(mappedButtons, rawButtons);
- mapCommonStartSelectMetaButtons(mappedButtons, rawButtons);
- mapXYAxes(mappedAxes, rawAxes);
-
- if (mLeftTriggerAxis != -1 && mRightTriggerAxis != -1) {
- float lTrigger = rawAxes[mLeftTriggerAxis];
- float rTrigger = rawAxes[mRightTriggerAxis];
- mappedButtons[CanonicalButtonIndex.LEFT_TRIGGER] = lTrigger;
- mappedButtons[CanonicalButtonIndex.RIGHT_TRIGGER] = rTrigger;
- } else {
- // Devices without analog triggers use digital buttons
- mapLowerTriggerButtonsToBottomShoulder(mappedButtons, rawButtons);
- }
-
- if (mRightStickXAxis != -1 && mRightStickYAxis != -1) {
- float rX = rawAxes[mRightStickXAxis];
- float rY = rawAxes[mRightStickYAxis];
- mappedAxes[CanonicalAxisIndex.RIGHT_STICK_X] = rX;
- mappedAxes[CanonicalAxisIndex.RIGHT_STICK_Y] = rY;
- }
-
- if (mUseHatAxes) {
- mapHatAxisToDpadButtons(mappedButtons, rawAxes);
- } else {
- mapCommonDpadButtons(mappedButtons, rawButtons);
- }
- }
- }
-}
diff --git a/chromium/device/gamepad/gamepad_provider.cc b/chromium/device/gamepad/gamepad_provider.cc
index 89439c77e92..ecc58373ba9 100644
--- a/chromium/device/gamepad/gamepad_provider.cc
+++ b/chromium/device/gamepad/gamepad_provider.cc
@@ -29,6 +29,8 @@
namespace device {
+constexpr int64_t kPollingIntervalMilliseconds = 4; // ~250 Hz
+
GamepadProvider::GamepadProvider(
GamepadConnectionChangeClient* connection_change_client)
: gamepad_shared_buffer_(std::make_unique<GamepadSharedBuffer>()),
@@ -139,7 +141,7 @@ void GamepadProvider::OnDevicesChanged(base::SystemMonitor::DeviceType type) {
void GamepadProvider::Initialize(std::unique_ptr<GamepadDataFetcher> fetcher) {
sampling_interval_delta_ =
- base::TimeDelta::FromMilliseconds(features::GetGamepadPollingInterval());
+ base::TimeDelta::FromMilliseconds(kPollingIntervalMilliseconds);
base::SystemMonitor* monitor = base::SystemMonitor::Get();
if (monitor)
diff --git a/chromium/device/gamepad/public/cpp/gamepad_features.cc b/chromium/device/gamepad/public/cpp/gamepad_features.cc
index c74ddbb2582..d942501706e 100644
--- a/chromium/device/gamepad/public/cpp/gamepad_features.cc
+++ b/chromium/device/gamepad/public/cpp/gamepad_features.cc
@@ -15,24 +15,6 @@
namespace features {
-namespace {
-
-const size_t kPollingIntervalMillisecondsMin = 4; // ~250 Hz
-const size_t kPollingIntervalMillisecondsMax = 16; // ~62.5 Hz
-
-size_t OverrideIntervalIfValid(base::StringPiece param_value,
- size_t default_interval) {
- size_t interval;
- if (param_value.empty() || !base::StringToSizeT(param_value, &interval))
- return default_interval;
- // Clamp interval duration to valid range.
- interval = std::max(interval, kPollingIntervalMillisecondsMin);
- interval = std::min(interval, kPollingIntervalMillisecondsMax);
- return interval;
-}
-
-} // namespace
-
// Enables gamepadbuttondown, gamepadbuttonup, gamepadbuttonchange,
// gamepadaxismove non-standard gamepad events.
const base::Feature kEnableGamepadButtonAxisEvents{
@@ -42,15 +24,9 @@ const base::Feature kEnableGamepadButtonAxisEvents{
const base::Feature kEnableWindowsGamingInputDataFetcher{
"EnableWindowsGamingInputDataFetcher", base::FEATURE_DISABLED_BY_DEFAULT};
-// Overrides the gamepad polling interval.
-const base::Feature kGamepadPollingInterval{"GamepadPollingInterval",
- base::FEATURE_DISABLED_BY_DEFAULT};
-
const base::Feature kRestrictGamepadAccess{"RestrictGamepadAccess",
base::FEATURE_DISABLED_BY_DEFAULT};
-const char kGamepadPollingIntervalParamKey[] = "interval-ms";
-
bool AreGamepadButtonAxisEventsEnabled() {
// Check if button and axis events are enabled by a field trial.
if (base::FeatureList::IsEnabled(kEnableGamepadButtonAxisEvents))
@@ -66,27 +42,4 @@ bool AreGamepadButtonAxisEventsEnabled() {
return false;
}
-size_t GetGamepadPollingInterval() {
- // Default to the minimum polling interval.
- size_t polling_interval = kPollingIntervalMillisecondsMin;
-
- // Check if the polling interval is overridden by a field trial.
- if (base::FeatureList::IsEnabled(kGamepadPollingInterval)) {
- std::string param_value = base::GetFieldTrialParamValueByFeature(
- kGamepadPollingInterval, kGamepadPollingIntervalParamKey);
- polling_interval = OverrideIntervalIfValid(param_value, polling_interval);
- }
-
- // Check if the polling interval is overridden by a command-line flag.
- base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
- if (command_line &&
- command_line->HasSwitch(switches::kGamepadPollingInterval)) {
- std::string switch_value =
- command_line->GetSwitchValueASCII(switches::kGamepadPollingInterval);
- polling_interval = OverrideIntervalIfValid(switch_value, polling_interval);
- }
-
- return polling_interval;
-}
-
} // namespace features
diff --git a/chromium/device/gamepad/public/cpp/gamepad_features.h b/chromium/device/gamepad/public/cpp/gamepad_features.h
index d5fc29c7fc8..12e857660f6 100644
--- a/chromium/device/gamepad/public/cpp/gamepad_features.h
+++ b/chromium/device/gamepad/public/cpp/gamepad_features.h
@@ -14,12 +14,9 @@ GAMEPAD_FEATURES_EXPORT extern const base::Feature
kEnableGamepadButtonAxisEvents;
GAMEPAD_FEATURES_EXPORT extern const base::Feature
kEnableWindowsGamingInputDataFetcher;
-GAMEPAD_FEATURES_EXPORT extern const base::Feature kGamepadPollingInterval;
GAMEPAD_FEATURES_EXPORT extern const base::Feature kRestrictGamepadAccess;
-GAMEPAD_FEATURES_EXPORT extern const char kGamepadPollingIntervalParamKey[];
GAMEPAD_FEATURES_EXPORT bool AreGamepadButtonAxisEventsEnabled();
-GAMEPAD_FEATURES_EXPORT size_t GetGamepadPollingInterval();
} // namespace features
diff --git a/chromium/device/gamepad/udev_gamepad_linux.cc b/chromium/device/gamepad/udev_gamepad_linux.cc
index 8845f575b22..e03e0da0344 100644
--- a/chromium/device/gamepad/udev_gamepad_linux.cc
+++ b/chromium/device/gamepad/udev_gamepad_linux.cc
@@ -26,6 +26,11 @@ bool DeviceIndexFromDevicePath(base::StringPiece path,
return base::StringToInt(index_str, index);
}
+// Small helper to avoid constructing a `StringPiece` from nullptr.
+base::StringPiece ToStringPiece(const char* str) {
+ return str ? base::StringPiece(str) : base::StringPiece();
+}
+
} // namespace
const char UdevGamepadLinux::kInputSubsystem[] = "input";
@@ -49,11 +54,11 @@ std::unique_ptr<UdevGamepadLinux> UdevGamepadLinux::Create(udev_device* dev) {
if (!dev)
return nullptr;
- const base::StringPiece node_path = device::udev_device_get_devnode(dev);
+ const auto node_path = ToStringPiece(device::udev_device_get_devnode(dev));
if (node_path.empty())
return nullptr;
- const base::StringPiece node_syspath = device::udev_device_get_syspath(dev);
+ const auto node_syspath = ToStringPiece(device::udev_device_get_syspath(dev));
if (node_syspath.empty())
return nullptr;
@@ -62,7 +67,7 @@ std::unique_ptr<UdevGamepadLinux> UdevGamepadLinux::Create(udev_device* dev) {
device::udev_device_get_parent_with_subsystem_devtype(
dev, kInputSubsystem, nullptr);
if (parent_dev)
- parent_syspath = device::udev_device_get_syspath(parent_dev);
+ parent_syspath = ToStringPiece(device::udev_device_get_syspath(parent_dev));
for (const auto& entry : device_roots) {
const Type node_type = entry.first;
diff --git a/chromium/device/vr/BUILD.gn b/chromium/device/vr/BUILD.gn
index 5a2f428a561..8f0b6e6d21a 100644
--- a/chromium/device/vr/BUILD.gn
+++ b/chromium/device/vr/BUILD.gn
@@ -8,12 +8,33 @@ if (is_android) {
import("//build/config/android/rules.gni") # For generate_jni().
}
+config("vr_gl_mode") {
+ if (use_command_buffer) {
+ defines = [
+ "VR_USE_COMMAND_BUFFER",
+ "GL_GLEXT_PROTOTYPES",
+ ]
+ } else {
+ defines = [ "VR_USE_NATIVE_GL" ]
+ }
+}
+
+source_set("vr_gl_bindings") {
+ sources = [ "gl_bindings.h" ]
+ public_configs = [ ":vr_gl_mode" ]
+ if (use_command_buffer) {
+ public_deps = [ "//gpu/command_buffer/client:gles2_c_lib" ]
+ } else {
+ public_deps = [ "//ui/gl" ]
+ }
+}
+
if (enable_vr) {
# TODO(https://crbug.com/1073113): Flesh out and cleanup this target.
component("vr_base") {
visibility = [
# TODO(https://crbug.com/843374): Move arcore_device
- "//chrome/browser/android/vr/*",
+ "//chrome/browser/*",
"//content/services/isolated_xr_device/*",
"//device/vr/*",
]
@@ -25,9 +46,13 @@ if (enable_vr) {
"vr_device.h",
"vr_device_base.cc",
"vr_device_base.h",
+ "vr_gl_util.cc",
+ "vr_gl_util.h",
]
+ public_configs = [ ":vr_gl_mode" ]
public_deps = [
+ ":vr_gl_bindings",
"//device/vr/public/cpp",
"//device/vr/public/mojom",
]
@@ -100,37 +125,15 @@ if (enable_vr) {
configs += [ "//third_party/gvr-android-sdk:libgvr_config" ]
}
- if (enable_openvr) {
- if (!is_component_build) {
- defines += [ "OPENVR_BUILD_STATIC" ]
- }
-
- deps += [ "//third_party/openvr:openvr" ]
+ if (enable_oculus_vr || enable_windows_mr || enable_openxr) {
sources += [
- "openvr/openvr_api_wrapper.cc",
- "openvr/openvr_api_wrapper.h",
- "openvr/openvr_device.cc",
- "openvr/openvr_device.h",
- "openvr/openvr_gamepad_helper.cc",
- "openvr/openvr_gamepad_helper.h",
- "openvr/openvr_render_loop.cc",
- "openvr/openvr_render_loop.h",
- "openvr/openvr_type_converters.cc",
- "openvr/openvr_type_converters.h",
"test/test_hook.h",
- ]
- }
-
- if (enable_openvr || enable_oculus_vr || enable_windows_mr ||
- enable_openxr) {
- sources += [
"windows/compositor_base.cc",
"windows/compositor_base.h",
]
}
- if (is_win && (enable_openvr || enable_oculus_vr || enable_windows_mr ||
- enable_openxr)) {
+ if (is_win && (enable_oculus_vr || enable_windows_mr || enable_openxr)) {
libs = [
"d3d11.lib",
"DXGI.lib",
@@ -313,35 +316,6 @@ if (enable_vr) {
}
}
-if (enable_openvr) {
- shared_library("openvr_mock") {
- testonly = true
- output_name = "mock_vr_clients/bin/vrclient"
- if (target_cpu == "x64" && is_win) {
- output_name = "mock_vr_clients/bin/vrclient_x64"
- }
-
- sources = [
- "openvr/test/fake_openvr_impl_api.cc",
- "openvr/test/test_helper.cc",
- "openvr/test/test_helper.h",
- "test/test_hook.h",
- ]
-
- libs = [
- "d3d11.lib",
- "DXGI.lib",
- ]
-
- deps = [
- ":directx_helpers",
- "//base",
- "//device/vr/public/mojom:test_mojom",
- "//third_party/openvr:openvr_headers",
- ]
- }
-}
-
if (enable_openxr) {
# The OpenXR Loader by default looks for the path to the OpenXR Runtime from a
# registry key, which typically points to the OpenXR runtime installed on the
diff --git a/chromium/device/vr/DEPS b/chromium/device/vr/DEPS
index 7f0638354ca..66aa93959bb 100644
--- a/chromium/device/vr/DEPS
+++ b/chromium/device/vr/DEPS
@@ -8,7 +8,8 @@ include_rules = [
"+services/metrics/public/cpp/ukm_builders.h",
"+third_party/gvr-android-sdk/src",
"+third_party/libovr/src",
- "+third_party/openvr/src/headers/openvr.h",
+ "+third_party/skia/include/core/SkColor.h",
"+ui/display",
"+ui/gfx",
+ "+ui/gl/gl_bindings.h",
]
diff --git a/chromium/device/vr/README.md b/chromium/device/vr/README.md
index 42fa1a65895..7734f602fcb 100644
--- a/chromium/device/vr/README.md
+++ b/chromium/device/vr/README.md
@@ -17,13 +17,12 @@ towards OpenXR being the only API used on desktops.
| API | OS | Supports | Enabled by Default |
|-----------------------|:--------:|:--------:|:------------------:|
| OpenXR | Windows* | VR* | Yes** |
-| Windows Mixed Reality | Windows | VR | Yes |
| AR Core | Android | AR | Yes |
| Google VR | Android | VR | Yes |
+| Windows Mixed Reality | Windows | VR | No |
| Oculus | Windows | VR | No |
-| OpenVR | Windows | VR | No |
- - * OpenXR may support multiple OSes and AR use cases as well. Currently we
+ - \* OpenXR may support multiple OSes and AR use cases as well. Currently we
only use it for VR on Windows since that's what the majority of existing
runtimes support.
- ** OpenXR runtimes are only enabled by default if they implement the
diff --git a/chromium/device/vr/android/BUILD.gn b/chromium/device/vr/android/BUILD.gn
new file mode 100644
index 00000000000..37f58947431
--- /dev/null
+++ b/chromium/device/vr/android/BUILD.gn
@@ -0,0 +1,17 @@
+# 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.
+
+static_library("vr_android") {
+ defines = []
+ sources = [
+ "mailbox_to_surface_bridge.h",
+ "web_xr_presentation_state.cc",
+ "web_xr_presentation_state.h",
+ ]
+
+ deps = [
+ "//gpu/ipc/common:common",
+ "//ui/gl:gl",
+ ]
+}
diff --git a/chromium/device/vr/android/DEPS b/chromium/device/vr/android/DEPS
new file mode 100644
index 00000000000..fefc58b8a5b
--- /dev/null
+++ b/chromium/device/vr/android/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+ "+gpu/ipc/common",
+ "+gpu/command_buffer/common/shared_image_usage.h",
+ "+ui/gl",
+]
diff --git a/chromium/device/vr/android/arcore/BUILD.gn b/chromium/device/vr/android/arcore/BUILD.gn
index 7676a20c359..e733c1cc580 100644
--- a/chromium/device/vr/android/arcore/BUILD.gn
+++ b/chromium/device/vr/android/arcore/BUILD.gn
@@ -11,16 +11,30 @@ component("arcore") {
defines = [ "IS_VR_ARCORE_IMPL" ]
sources = [
"address_to_id_map.h",
+ "ar_image_transport.cc",
+ "ar_image_transport.h",
+ "ar_renderer.cc",
+ "ar_renderer.h",
+ "arcore.cc",
"arcore.h",
"arcore_anchor_manager.cc",
"arcore_anchor_manager.h",
+ "arcore_device.cc",
+ "arcore_device.h",
"arcore_device_provider_factory.cc",
"arcore_device_provider_factory.h",
+ "arcore_gl.cc",
+ "arcore_gl.h",
+ "arcore_gl_thread.cc",
+ "arcore_gl_thread.h",
"arcore_impl.cc",
"arcore_impl.h",
+ "arcore_math_utils.cc",
+ "arcore_math_utils.h",
"arcore_plane_manager.cc",
"arcore_plane_manager.h",
"arcore_sdk.h",
+ "arcore_session_utils.h",
"arcore_shim.cc",
"arcore_shim.h",
"scoped_arcore_objects.h",
@@ -32,9 +46,12 @@ component("arcore") {
deps = [
"//base",
+ "//device/vr:vr",
"//device/vr:vr_base",
+ "//device/vr/android:vr_android",
"//mojo/public/cpp/bindings",
"//ui/gfx",
+ "//ui/gl/init",
]
configs += [ "//third_party/arcore-android-sdk:libarcore_config" ]
diff --git a/chromium/device/vr/android/arcore/DEPS b/chromium/device/vr/android/arcore/DEPS
index efd700cc2a1..5e9ba59297a 100644
--- a/chromium/device/vr/android/arcore/DEPS
+++ b/chromium/device/vr/android/arcore/DEPS
@@ -1 +1,3 @@
-include_rules = [ "+third_party/arcore-android-sdk/src", ] \ No newline at end of file
+include_rules = [
+ "+third_party/arcore-android-sdk/src",
+ ] \ No newline at end of file
diff --git a/chromium/device/vr/android/arcore/ar_image_transport.cc b/chromium/device/vr/android/arcore/ar_image_transport.cc
new file mode 100644
index 00000000000..6d063b6a439
--- /dev/null
+++ b/chromium/device/vr/android/arcore/ar_image_transport.cc
@@ -0,0 +1,408 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/vr/android/arcore/ar_image_transport.h"
+
+#include "base/android/android_hardware_buffer_compat.h"
+#include "base/android/scoped_hardware_buffer_handle.h"
+#include "base/containers/queue.h"
+#include "base/trace_event/trace_event.h"
+#include "base/trace_event/traced_value.h"
+#include "device/vr/android/mailbox_to_surface_bridge.h"
+#include "device/vr/android/web_xr_presentation_state.h"
+#include "gpu/command_buffer/common/shared_image_usage.h"
+#include "gpu/ipc/common/gpu_memory_buffer_impl_android_hardware_buffer.h"
+#include "ui/gfx/gpu_fence.h"
+#include "ui/gl/android/scoped_java_surface.h"
+#include "ui/gl/android/surface_texture.h"
+#include "ui/gl/gl_bindings.h"
+#include "ui/gl/gl_context.h"
+#include "ui/gl/gl_fence_egl.h"
+#include "ui/gl/gl_image_ahardwarebuffer.h"
+#include "ui/gl/gl_surface.h"
+#include "ui/gl/init/gl_factory.h"
+
+namespace device {
+
+ArImageTransport::ArImageTransport(
+ std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge)
+ : gl_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
+ mailbox_bridge_(std::move(mailbox_bridge)) {
+ DVLOG(2) << __func__;
+}
+
+ArImageTransport::~ArImageTransport() = default;
+
+void ArImageTransport::DestroySharedBuffers(vr::WebXrPresentationState* webxr) {
+ DVLOG(2) << __func__;
+ DCHECK(IsOnGlThread());
+
+ if (!webxr || !UseSharedBuffer())
+ return;
+
+ std::vector<std::unique_ptr<vr::WebXrSharedBuffer>> buffers =
+ webxr->TakeSharedBuffers();
+ for (auto& buffer : buffers) {
+ if (!buffer->mailbox_holder.mailbox.IsZero()) {
+ DCHECK(mailbox_bridge_);
+ DVLOG(2) << ": DestroySharedImage, mailbox="
+ << buffer->mailbox_holder.mailbox.ToDebugString();
+ // Note: the sync token in mailbox_holder may not be accurate. See
+ // comment in TransferFrame below.
+ mailbox_bridge_->DestroySharedImage(buffer->mailbox_holder);
+ }
+ }
+}
+
+void ArImageTransport::Initialize(vr::WebXrPresentationState* webxr,
+ base::OnceClosure callback) {
+ DCHECK(IsOnGlThread());
+ DVLOG(2) << __func__;
+
+ glDisable(GL_DEPTH_TEST);
+ glDepthMask(GL_FALSE);
+ ar_renderer_ = std::make_unique<ArRenderer>();
+ glGenTextures(1, &camera_texture_id_arcore_);
+
+ glGenFramebuffersEXT(1, &camera_fbo_);
+
+ // When available (Android O and up), use AHardwareBuffer-based shared
+ // images for frame transport.
+ shared_buffer_draw_ = base::AndroidHardwareBufferCompat::IsSupportAvailable();
+
+ if (shared_buffer_draw_) {
+ DVLOG(2) << __func__ << ": UseSharedBuffer()=true";
+ } else {
+ DVLOG(2) << __func__ << ": UseSharedBuffer()=false, setting up surface";
+ glGenTextures(1, &transport_texture_id_);
+ transport_surface_texture_ =
+ gl::SurfaceTexture::Create(transport_texture_id_);
+ surface_size_ = {0, 0};
+ mailbox_bridge_->CreateSurface(transport_surface_texture_.get());
+ transport_surface_texture_->SetFrameAvailableCallback(base::BindRepeating(
+ &ArImageTransport::OnFrameAvailable, weak_ptr_factory_.GetWeakPtr()));
+ }
+
+ mailbox_bridge_->CreateAndBindContextProvider(
+ base::BindOnce(&ArImageTransport::OnMailboxBridgeReady,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void ArImageTransport::OnMailboxBridgeReady(base::OnceClosure callback) {
+ DVLOG(2) << __func__;
+ DCHECK(IsOnGlThread());
+
+ DCHECK(mailbox_bridge_->IsConnected());
+
+ std::move(callback).Run();
+}
+
+void ArImageTransport::SetFrameAvailableCallback(
+ XrFrameCallback on_transport_frame_available) {
+ DVLOG(2) << __func__;
+ on_transport_frame_available_ = std::move(on_transport_frame_available);
+}
+
+void ArImageTransport::OnFrameAvailable() {
+ DVLOG(2) << __func__;
+ DCHECK(on_transport_frame_available_);
+
+ // This function assumes that there's only at most one frame in "processing"
+ // state at any given time, the webxr_ state handling ensures that. Drawing
+ // and swapping twice without an intervening UpdateTexImage call would lose
+ // an image, and that would lead to images and poses getting out of sync.
+ //
+ // It also assumes that the ArImageTransport and Surface only exist for the
+ // duration of a single session, and a new session will use fresh objects. For
+ // comparison, see GvrSchedulerDelegate::OnWebXrFrameAvailable which has more
+ // complex logic to support a lifetime across multiple sessions, including
+ // handling a possibly-unconsumed frame left over from a previous session.
+
+ transport_surface_texture_->UpdateTexImage();
+
+ // The SurfaceTexture needs to be drawn using the corresponding
+ // UV transform, that's usually a Y flip.
+ transport_surface_texture_->GetTransformMatrix(
+ &transport_surface_texture_uv_matrix_[0]);
+ transport_surface_texture_uv_transform_.matrix().setColMajorf(
+ transport_surface_texture_uv_matrix_);
+
+ on_transport_frame_available_.Run(transport_surface_texture_uv_transform_);
+}
+
+GLuint ArImageTransport::GetCameraTextureId() {
+ return camera_texture_id_arcore_;
+}
+
+bool ArImageTransport::ResizeSharedBuffer(vr::WebXrPresentationState* webxr,
+ const gfx::Size& size,
+ vr::WebXrSharedBuffer* buffer) {
+ DCHECK(IsOnGlThread());
+
+ if (buffer->size == size)
+ return false;
+
+ TRACE_EVENT0("gpu", __FUNCTION__);
+ // Unbind previous image (if any).
+ if (!buffer->mailbox_holder.mailbox.IsZero()) {
+ DVLOG(2) << ": DestroySharedImage, mailbox="
+ << buffer->mailbox_holder.mailbox.ToDebugString();
+ // Note: the sync token in mailbox_holder may not be accurate. See comment
+ // in TransferFrame below.
+ mailbox_bridge_->DestroySharedImage(buffer->mailbox_holder);
+ }
+
+ DVLOG(2) << __FUNCTION__ << ": width=" << size.width()
+ << " height=" << size.height();
+ // Remove reference to previous image (if any).
+ buffer->local_glimage = nullptr;
+
+ static constexpr gfx::BufferFormat format = gfx::BufferFormat::RGBA_8888;
+ static constexpr gfx::BufferUsage usage = gfx::BufferUsage::SCANOUT;
+
+ gfx::GpuMemoryBufferId kBufferId(webxr->next_memory_buffer_id++);
+ buffer->gmb = gpu::GpuMemoryBufferImplAndroidHardwareBuffer::Create(
+ kBufferId, size, format, usage,
+ gpu::GpuMemoryBufferImpl::DestructionCallback());
+
+ uint32_t shared_image_usage = gpu::SHARED_IMAGE_USAGE_SCANOUT |
+ gpu::SHARED_IMAGE_USAGE_DISPLAY |
+ gpu::SHARED_IMAGE_USAGE_GLES2;
+ buffer->mailbox_holder = mailbox_bridge_->CreateSharedImage(
+ buffer->gmb.get(), gfx::ColorSpace(), shared_image_usage);
+ DVLOG(2) << ": CreateSharedImage, mailbox="
+ << buffer->mailbox_holder.mailbox.ToDebugString() << ", SyncToken="
+ << buffer->mailbox_holder.sync_token.ToDebugString();
+
+ auto img = base::MakeRefCounted<gl::GLImageAHardwareBuffer>(size);
+
+ base::android::ScopedHardwareBufferHandle ahb =
+ buffer->gmb->CloneHandle().android_hardware_buffer;
+ bool ret = img->Initialize(ahb.get(), false /* preserved */);
+ if (!ret) {
+ DLOG(WARNING) << __FUNCTION__ << ": ERROR: failed to initialize image!";
+ return false;
+ }
+ glBindTexture(GL_TEXTURE_EXTERNAL_OES, buffer->local_texture);
+ img->BindTexImage(GL_TEXTURE_EXTERNAL_OES);
+ buffer->local_glimage = std::move(img);
+
+ // Save size to avoid resize next time.
+ DVLOG(1) << __FUNCTION__ << ": resized to " << size.width() << "x"
+ << size.height();
+ buffer->size = size;
+ return true;
+}
+
+std::unique_ptr<vr::WebXrSharedBuffer> ArImageTransport::CreateBuffer() {
+ std::unique_ptr<vr::WebXrSharedBuffer> buffer =
+ std::make_unique<vr::WebXrSharedBuffer>();
+ // Local resources
+ glGenTextures(1, &buffer->local_texture);
+ return buffer;
+}
+
+gpu::MailboxHolder ArImageTransport::TransferFrame(
+ vr::WebXrPresentationState* webxr,
+ const gfx::Size& frame_size,
+ const gfx::Transform& uv_transform) {
+ DCHECK(IsOnGlThread());
+ DCHECK(UseSharedBuffer());
+
+ if (!webxr->GetAnimatingFrame()->shared_buffer) {
+ webxr->GetAnimatingFrame()->shared_buffer = CreateBuffer();
+ }
+
+ vr::WebXrSharedBuffer* shared_buffer =
+ webxr->GetAnimatingFrame()->shared_buffer.get();
+ ResizeSharedBuffer(webxr, frame_size, shared_buffer);
+ // Sanity check that the lazily created/resized buffer looks valid.
+ DCHECK(!shared_buffer->mailbox_holder.mailbox.IsZero());
+ DCHECK(shared_buffer->local_glimage);
+ DCHECK_EQ(shared_buffer->local_glimage->GetSize(), frame_size);
+
+ // We don't need to create a sync token here. ResizeSharedBuffer has created
+ // one on reallocation, including initial buffer creation, and we can use
+ // that. The shared image interface internally uses its own command buffer ID
+ // and separate sync token release count namespace, and we must not overwrite
+ // that. We don't need a new sync token when reusing a correctly-sized buffer,
+ // it's only eligible for reuse after all reads from it are complete, meaning
+ // that it's transitioned through "processing" and "rendering" states back
+ // to "animating".
+ DCHECK(shared_buffer->mailbox_holder.sync_token.HasData());
+ DVLOG(2) << ": SyncToken="
+ << shared_buffer->mailbox_holder.sync_token.ToDebugString();
+
+ return shared_buffer->mailbox_holder;
+}
+
+gpu::MailboxHolder ArImageTransport::TransferCameraImageFrame(
+ vr::WebXrPresentationState* webxr,
+ const gfx::Size& frame_size,
+ const gfx::Transform& uv_transform) {
+ DCHECK(IsOnGlThread());
+ DCHECK(UseSharedBuffer());
+
+ if (!webxr->GetAnimatingFrame()->camera_image_shared_buffer) {
+ webxr->GetAnimatingFrame()->camera_image_shared_buffer = CreateBuffer();
+ }
+
+ vr::WebXrSharedBuffer* camera_image_shared_buffer =
+ webxr->GetAnimatingFrame()->camera_image_shared_buffer.get();
+ bool was_resized =
+ ResizeSharedBuffer(webxr, frame_size, camera_image_shared_buffer);
+ if (was_resized) {
+ // Ensure that the following GPU command buffer actions are sequenced after
+ // the shared buffer operations. The shared image interface uses a separate
+ // command buffer stream.
+ DCHECK(camera_image_shared_buffer->mailbox_holder.sync_token.HasData());
+ WaitSyncToken(camera_image_shared_buffer->mailbox_holder.sync_token);
+ DVLOG(3) << __func__
+ << ": "
+ "camera_image_shared_buffer->mailbox_holder.sync_"
+ "token="
+ << camera_image_shared_buffer->mailbox_holder.sync_token
+ .ToDebugString();
+ }
+ // Sanity checks for the camera image buffer.
+ DCHECK(!camera_image_shared_buffer->mailbox_holder.mailbox.IsZero());
+ DCHECK(camera_image_shared_buffer->local_glimage);
+ DCHECK_EQ(camera_image_shared_buffer->local_glimage->GetSize(), frame_size);
+
+ // Temporarily change drawing buffer to the camera image buffer.
+ if (!camera_image_fbo_) {
+ glGenFramebuffersEXT(1, &camera_image_fbo_);
+ }
+ glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, camera_image_fbo_);
+ glFramebufferTexture2DEXT(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_EXTERNAL_OES,
+ camera_image_shared_buffer->local_texture, 0);
+
+ CopyCameraImageToFramebuffer(frame_size, uv_transform);
+
+#if DCHECK_IS_ON()
+ if (!framebuffer_complete_checked_for_camera_buffer_) {
+ auto status = glCheckFramebufferStatusEXT(GL_DRAW_FRAMEBUFFER);
+ DVLOG(1) << __func__ << ": framebuffer status=" << std::hex << status;
+ DCHECK(status == GL_FRAMEBUFFER_COMPLETE);
+ framebuffer_complete_checked_for_camera_buffer_ = true;
+ }
+#endif
+
+ // Restore default drawing buffer.
+ glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, 0);
+
+ std::unique_ptr<gl::GLFence> gl_fence = gl::GLFence::CreateForGpuFence();
+ std::unique_ptr<gfx::GpuFence> gpu_fence = gl_fence->GetGpuFence();
+ mailbox_bridge_->WaitForClientGpuFence(gpu_fence.release());
+
+ mailbox_bridge_->GenSyncToken(
+ &camera_image_shared_buffer->mailbox_holder.sync_token);
+ DVLOG(3)
+ << __func__ << ": camera_image_shared_buffer->mailbox_holder.sync_token="
+ << camera_image_shared_buffer->mailbox_holder.sync_token.ToDebugString();
+ return camera_image_shared_buffer->mailbox_holder;
+}
+
+void ArImageTransport::CreateGpuFenceForSyncToken(
+ const gpu::SyncToken& sync_token,
+ base::OnceCallback<void(std::unique_ptr<gfx::GpuFence>)> callback) {
+ DVLOG(2) << __func__;
+ mailbox_bridge_->CreateGpuFence(sync_token, std::move(callback));
+}
+
+void ArImageTransport::WaitSyncToken(const gpu::SyncToken& sync_token) {
+ mailbox_bridge_->WaitSyncToken(sync_token);
+}
+
+void ArImageTransport::CopyCameraImageToFramebuffer(
+ const gfx::Size& frame_size,
+ const gfx::Transform& uv_transform) {
+ glDisable(GL_BLEND);
+ CopyTextureToFramebuffer(camera_texture_id_arcore_, frame_size, uv_transform);
+}
+
+void ArImageTransport::ServerWaitForGpuFence(
+ std::unique_ptr<gfx::GpuFence> gpu_fence) {
+ std::unique_ptr<gl::GLFence> local_fence =
+ gl::GLFence::CreateFromGpuFence(*gpu_fence);
+ local_fence->ServerWait();
+}
+
+void ArImageTransport::CopyDrawnImageToFramebuffer(
+ vr::WebXrPresentationState* webxr,
+ const gfx::Size& frame_size,
+ const gfx::Transform& uv_transform) {
+ DVLOG(2) << __func__;
+
+ GLuint source_texture;
+ if (UseSharedBuffer()) {
+ vr::WebXrSharedBuffer* shared_buffer =
+ webxr->GetRenderingFrame()->shared_buffer.get();
+ source_texture = shared_buffer->local_texture;
+ } else {
+ source_texture = transport_texture_id_;
+ }
+
+ // Set the blend mode for combining the drawn image (source) with the camera
+ // image (destination). WebXR assumes that the canvas has premultiplied alpha,
+ // so the source blend function is GL_ONE. The destination blend function is
+ // (1 - src_alpha) as usual. (Setting that to GL_ONE would simulate an
+ // additive AR headset that can't draw opaque black.)
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+ glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, 0);
+ CopyTextureToFramebuffer(source_texture, frame_size, uv_transform);
+}
+
+void ArImageTransport::CopyTextureToFramebuffer(
+ GLuint texture,
+ const gfx::Size& frame_size,
+ const gfx::Transform& uv_transform) {
+ DVLOG(2) << __func__;
+ // Don't need face culling, depth testing, blending, etc. Turn it all off.
+ // TODO(klausw): see if we can do this one time on initialization. That would
+ // be a tiny bit more efficient, but is only safe if ARCore and ArRenderer
+ // don't modify these states.
+ glDisable(GL_CULL_FACE);
+ glDisable(GL_SCISSOR_TEST);
+ glDisable(GL_POLYGON_OFFSET_FILL);
+ glViewport(0, 0, frame_size.width(), frame_size.height());
+
+ // Draw the ARCore texture!
+ float uv_transform_floats[16];
+ uv_transform.matrix().asColMajorf(uv_transform_floats);
+ ar_renderer_->Draw(texture, uv_transform_floats);
+}
+
+void ArImageTransport::CopyMailboxToSurfaceAndSwap(
+ const gfx::Size& frame_size,
+ const gpu::MailboxHolder& mailbox) {
+ DVLOG(2) << __func__;
+ if (frame_size != surface_size_) {
+ DVLOG(2) << __func__ << " resize from " << surface_size_.ToString()
+ << " to " << frame_size.ToString();
+ transport_surface_texture_->SetDefaultBufferSize(frame_size.width(),
+ frame_size.height());
+ mailbox_bridge_->ResizeSurface(frame_size.width(), frame_size.height());
+ surface_size_ = frame_size;
+ }
+
+ // Draw the image to the surface in the GPU process's command buffer context.
+ // This will trigger an OnFrameAvailable event once the corresponding
+ // SurfaceTexture in the local GL context is ready for updating.
+ bool swapped = mailbox_bridge_->CopyMailboxToSurfaceAndSwap(mailbox);
+ DCHECK(swapped);
+}
+
+bool ArImageTransport::IsOnGlThread() const {
+ return gl_thread_task_runner_->BelongsToCurrentThread();
+}
+
+std::unique_ptr<ArImageTransport> ArImageTransportFactory::Create(
+ std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge) {
+ return std::make_unique<ArImageTransport>(std::move(mailbox_bridge));
+}
+
+} // namespace device
diff --git a/chromium/device/vr/android/arcore/ar_image_transport.h b/chromium/device/vr/android/arcore/ar_image_transport.h
new file mode 100644
index 00000000000..7796b94baab
--- /dev/null
+++ b/chromium/device/vr/android/arcore/ar_image_transport.h
@@ -0,0 +1,147 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_VR_ANDROID_ARCORE_AR_IMAGE_TRANSPORT_H_
+#define DEVICE_VR_ANDROID_ARCORE_AR_IMAGE_TRANSPORT_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/single_thread_task_runner.h"
+#include "device/vr/android/arcore/ar_renderer.h"
+#include "device/vr/public/mojom/vr_service.mojom.h"
+#include "ui/gfx/geometry/size_f.h"
+
+namespace gl {
+class SurfaceTexture;
+} // namespace gl
+
+namespace gfx {
+class GpuFence;
+} // namespace gfx
+
+namespace gpu {
+struct MailboxHolder;
+struct SyncToken;
+} // namespace gpu
+
+namespace vr {
+class WebXrPresentationState;
+struct WebXrSharedBuffer;
+} // namespace vr
+
+namespace device {
+
+class MailboxToSurfaceBridge;
+
+using XrFrameCallback = base::RepeatingCallback<void(const gfx::Transform&)>;
+
+// This class handles transporting WebGL rendered output from the GPU process's
+// command buffer GL context to the local GL context, and compositing WebGL
+// output onto the camera image using the local GL context.
+class COMPONENT_EXPORT(VR_ARCORE) ArImageTransport {
+ public:
+ explicit ArImageTransport(
+ std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge);
+ virtual ~ArImageTransport();
+
+ virtual void DestroySharedBuffers(vr::WebXrPresentationState* webxr);
+
+ // All methods must be called on a valid GL thread. Initialization
+ // must happen after the local GL context is ready for use. That
+ // starts the asynchronous setup for the GPU process command buffer
+ // GL context via MailboxToSurfaceBridge, and the callback is called
+ // once that's complete.
+ virtual void Initialize(vr::WebXrPresentationState* webxr,
+ base::OnceClosure callback);
+
+ virtual GLuint GetCameraTextureId();
+
+ // This creates a shared buffer if one doesn't already exist, and populates it
+ // with the current animating frame's buffer data. It returns a
+ // gpu::Mailboxholder with this shared buffer data.
+ virtual gpu::MailboxHolder TransferFrame(vr::WebXrPresentationState* webxr,
+ const gfx::Size& frame_size,
+ const gfx::Transform& uv_transform);
+
+ // This transfers whatever the contents of the texture specified
+ // by GetCameraTextureId() is at the time it is called and returns
+ // a gpu::MailboxHolder with that texture copied to a shared buffer.
+ virtual gpu::MailboxHolder TransferCameraImageFrame(
+ vr::WebXrPresentationState* webxr,
+ const gfx::Size& frame_size,
+ const gfx::Transform& uv_transform);
+
+ virtual void CreateGpuFenceForSyncToken(
+ const gpu::SyncToken& sync_token,
+ base::OnceCallback<void(std::unique_ptr<gfx::GpuFence>)>);
+ virtual void CopyCameraImageToFramebuffer(const gfx::Size& frame_size,
+ const gfx::Transform& uv_transform);
+ virtual void CopyDrawnImageToFramebuffer(vr::WebXrPresentationState* webxr,
+ const gfx::Size& frame_size,
+ const gfx::Transform& uv_transform);
+ virtual void CopyTextureToFramebuffer(GLuint texture,
+ const gfx::Size& frame_size,
+ const gfx::Transform& uv_transform);
+ virtual void WaitSyncToken(const gpu::SyncToken& sync_token);
+ virtual void CopyMailboxToSurfaceAndSwap(const gfx::Size& frame_size,
+ const gpu::MailboxHolder& mailbox);
+
+ bool UseSharedBuffer() { return shared_buffer_draw_; }
+ void SetFrameAvailableCallback(XrFrameCallback on_frame_available);
+ void ServerWaitForGpuFence(std::unique_ptr<gfx::GpuFence> gpu_fence);
+
+ private:
+ std::unique_ptr<vr::WebXrSharedBuffer> CreateBuffer();
+ // Returns true if the buffer was resized and its sync token updated.
+ bool ResizeSharedBuffer(vr::WebXrPresentationState* webxr,
+ const gfx::Size& size,
+ vr::WebXrSharedBuffer* buffer);
+ void ResizeSurface(const gfx::Size& size);
+ bool IsOnGlThread() const;
+ void OnMailboxBridgeReady(base::OnceClosure callback);
+ void OnFrameAvailable();
+ std::unique_ptr<ArRenderer> ar_renderer_;
+ // samplerExternalOES texture for the camera image.
+ GLuint camera_texture_id_arcore_ = 0;
+ GLuint camera_fbo_ = 0;
+ GLuint camera_image_fbo_ = 0;
+
+ scoped_refptr<base::SingleThreadTaskRunner> gl_thread_task_runner_;
+
+ std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge_;
+
+ // If true, use shared buffer transport aka DRAW_INTO_TEXTURE_MAILBOX.
+ // If false, use Surface transport aka SUBMIT_AS_MAILBOX_HOLDER.
+ bool shared_buffer_draw_ = false;
+
+ // Used to limit framebuffer complete check to occurring once, due to it being
+ // expensive.
+ bool framebuffer_complete_checked_for_camera_buffer_ = false;
+
+ // Used for Surface transport (Android N)
+ //
+ // samplerExternalOES texture data for WebXR content image.
+ GLuint transport_texture_id_ = 0;
+ gfx::Size surface_size_;
+ scoped_refptr<gl::SurfaceTexture> transport_surface_texture_;
+ gfx::Transform transport_surface_texture_uv_transform_;
+ float transport_surface_texture_uv_matrix_[16];
+ XrFrameCallback on_transport_frame_available_;
+
+ // Must be last.
+ base::WeakPtrFactory<ArImageTransport> weak_ptr_factory_{this};
+ DISALLOW_COPY_AND_ASSIGN(ArImageTransport);
+};
+
+class COMPONENT_EXPORT(VR_ARCORE) ArImageTransportFactory {
+ public:
+ virtual ~ArImageTransportFactory() = default;
+ virtual std::unique_ptr<ArImageTransport> Create(
+ std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge);
+};
+
+} // namespace device
+
+#endif // DEVICE_VR_ANDROID_ARCORE_AR_IMAGE_TRANSPORT_H_
diff --git a/chromium/device/vr/android/arcore/ar_renderer.cc b/chromium/device/vr/android/arcore/ar_renderer.cc
new file mode 100644
index 00000000000..1291a089bfb
--- /dev/null
+++ b/chromium/device/vr/android/arcore/ar_renderer.cc
@@ -0,0 +1,125 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/vr/android/arcore/ar_renderer.h"
+
+#include "base/stl_util.h"
+#include "device/vr/vr_gl_util.h"
+
+namespace device {
+
+namespace {
+
+static constexpr float kQuadVertices[8] = {
+ -0.5f, 0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f, -0.5f,
+};
+static constexpr GLushort kQuadIndices[6] = {0, 1, 2, 1, 3, 2};
+
+// clang-format off
+static constexpr char const* kVertexShader = SHADER(
+ precision mediump float;
+ uniform mat4 u_UvTransform;
+ attribute vec4 a_Position;
+ varying vec2 v_TexCoordinate;
+
+ void main() {
+ // The quad vertex coordinate range is [-0.5, 0.5]. Transform to [0, 1],
+ // then apply the supplied affine transform matrix to get the final UV.
+ float xposition = a_Position[0] + 0.5;
+ float yposition = a_Position[1] + 0.5;
+ vec4 uv_in = vec4(xposition, yposition, 0.0, 1.0);
+ vec4 uv_out = u_UvTransform * uv_in;
+ v_TexCoordinate = vec2(uv_out.x, uv_out.y);
+ gl_Position = vec4(a_Position.xyz * 2.0, 1.0);
+ }
+);
+
+static constexpr char const* kFragmentShader = OEIE_SHADER(
+ precision highp float;
+ uniform samplerExternalOES u_Texture;
+ varying vec2 v_TexCoordinate;
+
+ void main() {
+ gl_FragColor = texture2D(u_Texture, v_TexCoordinate);
+ }
+);
+// clang-format on
+
+} // namespace
+
+ArRenderer::ArRenderer() {
+ std::string error;
+ GLuint vertex_shader_handle =
+ vr::CompileShader(GL_VERTEX_SHADER, kVertexShader, error);
+ // TODO(crbug.com/866593): fail gracefully if shaders don't compile.
+ CHECK(vertex_shader_handle) << error << "\nvertex_src\n" << kVertexShader;
+
+ GLuint fragment_shader_handle =
+ vr::CompileShader(GL_FRAGMENT_SHADER, kFragmentShader, error);
+ CHECK(fragment_shader_handle) << error << "\nfragment_src\n"
+ << kFragmentShader;
+
+ program_handle_ = vr::CreateAndLinkProgram(vertex_shader_handle,
+ fragment_shader_handle, error);
+ CHECK(program_handle_) << error;
+
+ // Once the program is linked the shader objects are no longer needed
+ glDeleteShader(vertex_shader_handle);
+ glDeleteShader(fragment_shader_handle);
+
+ position_handle_ = glGetAttribLocation(program_handle_, "a_Position");
+ clip_rect_handle_ = glGetUniformLocation(program_handle_, "u_ClipRect");
+ texture_handle_ = glGetUniformLocation(program_handle_, "u_Texture");
+ uv_transform_ = glGetUniformLocation(program_handle_, "u_UvTransform");
+}
+
+void ArRenderer::Draw(int texture_handle, const float (&uv_transform)[16]) {
+ if (!vertex_buffer_ || !index_buffer_) {
+ GLuint buffers[2];
+ glGenBuffersARB(2, buffers);
+ vertex_buffer_ = buffers[0];
+ index_buffer_ = buffers[1];
+
+ glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
+ glBufferData(GL_ARRAY_BUFFER, base::size(kQuadVertices) * sizeof(float),
+ kQuadVertices, GL_STATIC_DRAW);
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer_);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER,
+ base::size(kQuadIndices) * sizeof(GLushort), kQuadIndices,
+ GL_STATIC_DRAW);
+ }
+
+ glUseProgram(program_handle_);
+
+ // Bind vertex attributes
+ glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_);
+
+ // Set up position attribute.
+ glVertexAttribPointer(position_handle_, 2, GL_FLOAT, false, 0, 0);
+ glEnableVertexAttribArray(position_handle_);
+
+ // Bind texture. This is a 1:1 pixel copy since the source surface
+ // and renderbuffer destination size are resized to match, so use
+ // GL_NEAREST.
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture_handle);
+ vr::SetTexParameters(GL_TEXTURE_EXTERNAL_OES);
+ glUniform1i(texture_handle_, 0);
+
+ glUniformMatrix4fv(uv_transform_, 1, GL_FALSE, &uv_transform[0]);
+
+ // Blit texture to buffer
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer_);
+ glDrawElements(GL_TRIANGLES, base::size(kQuadIndices), GL_UNSIGNED_SHORT, 0);
+
+ glDisableVertexAttribArray(position_handle_);
+}
+
+// Note that we don't explicitly delete gl objects here, they're deleted
+// automatically when we call ShutdownGL, and deleting them here leads to
+// segfaults.
+ArRenderer::~ArRenderer() = default;
+
+} // namespace device
diff --git a/chromium/device/vr/android/arcore/ar_renderer.h b/chromium/device/vr/android/arcore/ar_renderer.h
new file mode 100644
index 00000000000..b1365bcf780
--- /dev/null
+++ b/chromium/device/vr/android/arcore/ar_renderer.h
@@ -0,0 +1,36 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_VR_ANDROID_ARCORE_AR_RENDERER_H_
+#define DEVICE_VR_ANDROID_ARCORE_AR_RENDERER_H_
+
+#include "base/macros.h"
+#include "ui/gl/gl_bindings.h"
+
+namespace device {
+
+// Issues GL for rendering a texture for AR.
+// TODO(crbug.com/838013): Share code with WebVrRenderer.
+class ArRenderer {
+ public:
+ ArRenderer();
+ ~ArRenderer();
+
+ void Draw(int texture_handle, const float (&uv_transform)[16]);
+
+ private:
+ GLuint program_handle_ = 0;
+ GLuint position_handle_ = 0;
+ GLuint clip_rect_handle_ = 0;
+ GLuint texture_handle_ = 0;
+ GLuint uv_transform_ = 0;
+ GLuint vertex_buffer_ = 0;
+ GLuint index_buffer_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(ArRenderer);
+};
+
+} // namespace device
+
+#endif // DEVICE_VR_ANDROID_ARCORE_AR_RENDERER_H_
diff --git a/chromium/device/vr/android/arcore/arcore.cc b/chromium/device/vr/android/arcore/arcore.cc
new file mode 100644
index 00000000000..714b3b4db29
--- /dev/null
+++ b/chromium/device/vr/android/arcore/arcore.cc
@@ -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.
+
+#include "device/vr/android/arcore/arcore.h"
+
+#include "device/vr/android/arcore/arcore_math_utils.h"
+
+namespace device {
+
+gfx::Transform ArCore::GetCameraUvFromScreenUvTransform() const {
+ //
+ // Observe how kInputCoordinatesForTransform are transformed by ArCore,
+ // compute a matrix based on that and post-multiply with a matrix that
+ // performs a Y-flip.
+ //
+ // We need to add a Y flip because ArCore's
+ // AR_COORDINATES_2D_TEXTURE_NORMALIZED coordinates have the origin at the top
+ // left to match 2D Android APIs, so it needs a Y flip to get an origin at
+ // bottom left as used for textures.
+ // The post-multiplied matrix is performing a mapping: (x, y) -> (x, 1 - y).
+ //
+ return MatrixFromTransformedPoints(
+ TransformDisplayUvCoords(kInputCoordinatesForTransform)) *
+ gfx::Transform(1, 0, 0, -1, 0, 1);
+}
+
+} // namespace device
diff --git a/chromium/device/vr/android/arcore/arcore.h b/chromium/device/vr/android/arcore/arcore.h
index 787b6f18520..629ef1b8437 100644
--- a/chromium/device/vr/android/arcore/arcore.h
+++ b/chromium/device/vr/android/arcore/arcore.h
@@ -9,6 +9,7 @@
#include <vector>
#include "base/android/scoped_java_ref.h"
+#include "base/component_export.h"
#include "base/macros.h"
#include "base/optional.h"
#include "base/time/time.h"
@@ -20,22 +21,24 @@ namespace device {
// This allows a real or fake implementation of ArCore to
// be used as appropriate (i.e. for testing).
-class ArCore {
+class COMPONENT_EXPORT(VR_ARCORE) ArCore {
public:
virtual ~ArCore() = default;
// Initializes the runtime and returns whether it was successful.
// If successful, the runtime must be paused when this method returns.
virtual bool Initialize(
- base::android::ScopedJavaLocalRef<jobject> application_context) = 0;
+ base::android::ScopedJavaLocalRef<jobject> application_context,
+ const std::unordered_set<device::mojom::XRSessionFeature>&
+ enabled_features) = 0;
virtual void SetDisplayGeometry(
const gfx::Size& frame_size,
display::Display::Rotation display_rotation) = 0;
virtual void SetCameraTexture(uint32_t camera_texture_id) = 0;
- // Transform the given UV coordinates by the current display rotation.
- virtual std::vector<float> TransformDisplayUvCoords(
- const base::span<const float> uvs) = 0;
+
+ gfx::Transform GetCameraUvFromScreenUvTransform() const;
+
virtual gfx::Transform GetProjectionMatrix(float near, float far) = 0;
// Update ArCore state. This call blocks for up to 1/30s while waiting for a
@@ -61,6 +64,8 @@ class ArCore {
// Returns information about lighting estimation.
virtual mojom::XRLightEstimationDataPtr GetLightEstimationData() = 0;
+ virtual mojom::XRDepthDataPtr GetDepthData() = 0;
+
virtual bool RequestHitTest(
const mojom::XRRayPtr& ray,
std::vector<mojom::XRHitResultPtr>* hit_results) = 0;
@@ -136,6 +141,10 @@ class ArCore {
virtual void Pause() = 0;
virtual void Resume() = 0;
+
+ protected:
+ virtual std::vector<float> TransformDisplayUvCoords(
+ const base::span<const float> uvs) const = 0;
};
class ArCoreFactory {
diff --git a/chromium/device/vr/android/arcore/arcore_device.cc b/chromium/device/vr/android/arcore/arcore_device.cc
new file mode 100644
index 00000000000..6d8f9879bc1
--- /dev/null
+++ b/chromium/device/vr/android/arcore/arcore_device.cc
@@ -0,0 +1,343 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/vr/android/arcore/arcore_device.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/numerics/math_constants.h"
+#include "base/optional.h"
+#include "base/task/post_task.h"
+#include "base/trace_event/trace_event.h"
+#include "device/vr/android/arcore/ar_image_transport.h"
+#include "device/vr/android/arcore/arcore_gl.h"
+#include "device/vr/android/arcore/arcore_gl_thread.h"
+#include "device/vr/android/arcore/arcore_impl.h"
+#include "device/vr/android/arcore/arcore_session_utils.h"
+#include "device/vr/android/mailbox_to_surface_bridge.h"
+#include "ui/display/display.h"
+
+using base::android::JavaRef;
+
+namespace {
+constexpr float kDegreesPerRadian = 180.0f / base::kPiFloat;
+} // namespace
+
+namespace device {
+
+namespace {
+
+mojom::VRDisplayInfoPtr CreateVRDisplayInfo(const gfx::Size& frame_size) {
+ mojom::VRDisplayInfoPtr device = mojom::VRDisplayInfo::New();
+ device->left_eye = mojom::VREyeParameters::New();
+ device->right_eye = nullptr;
+ mojom::VREyeParametersPtr& left_eye = device->left_eye;
+ left_eye->field_of_view = mojom::VRFieldOfView::New();
+ // TODO(lincolnfrog): get these values for real (see gvr device).
+ double fov_x = 1437.387;
+ double fov_y = 1438.074;
+ // TODO(lincolnfrog): get real camera intrinsics.
+ int width = frame_size.width();
+ int height = frame_size.height();
+ float horizontal_degrees = atan(width / (2.0 * fov_x)) * kDegreesPerRadian;
+ float vertical_degrees = atan(height / (2.0 * fov_y)) * kDegreesPerRadian;
+ left_eye->field_of_view->left_degrees = horizontal_degrees;
+ left_eye->field_of_view->right_degrees = horizontal_degrees;
+ left_eye->field_of_view->up_degrees = vertical_degrees;
+ left_eye->field_of_view->down_degrees = vertical_degrees;
+ left_eye->render_width = width;
+ left_eye->render_height = height;
+ return device;
+}
+
+} // namespace
+
+ArCoreDevice::SessionState::SessionState() = default;
+ArCoreDevice::SessionState::~SessionState() = default;
+
+ArCoreDevice::ArCoreDevice(
+ std::unique_ptr<ArCoreFactory> arcore_factory,
+ std::unique_ptr<ArImageTransportFactory> ar_image_transport_factory,
+ std::unique_ptr<MailboxToSurfaceBridgeFactory>
+ mailbox_to_surface_bridge_factory,
+ std::unique_ptr<vr::ArCoreSessionUtils> arcore_session_utils)
+ : VRDeviceBase(mojom::XRDeviceId::ARCORE_DEVICE_ID),
+ main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
+ arcore_factory_(std::move(arcore_factory)),
+ ar_image_transport_factory_(std::move(ar_image_transport_factory)),
+ mailbox_bridge_factory_(std::move(mailbox_to_surface_bridge_factory)),
+ arcore_session_utils_(std::move(arcore_session_utils)),
+ mailbox_bridge_(mailbox_bridge_factory_->Create()),
+ session_state_(std::make_unique<ArCoreDevice::SessionState>()) {
+ // Ensure display_info_ is set to avoid crash in CallDeferredSessionCallback
+ // if initialization fails. Use an arbitrary but really low resolution to make
+ // it obvious if we're using this data instead of the actual values we get
+ // from the output drawing surface.
+ SetVRDisplayInfo(CreateVRDisplayInfo({16, 16}));
+}
+
+ArCoreDevice::~ArCoreDevice() {
+ // If there's still a pending session request, reject it.
+ CallDeferredRequestSessionCallback(/*success=*/false);
+
+ // Ensure that any active sessions are terminated. Terminating the GL thread
+ // would normally do so via its session_shutdown_callback_, but that happens
+ // asynchronously via CreateMainThreadCallback, and it doesn't seem safe to
+ // depend on all posted tasks being handled before the thread is shut down.
+ // Repeated EndSession calls are a no-op, so it's OK to do this redundantly.
+ OnSessionEnded();
+
+ // The GL thread must be terminated since it uses our members. For example,
+ // there might still be a posted Initialize() call in flight that uses
+ // arcore_session_utils_ and arcore_factory_. Ensure that the thread is
+ // stopped before other members get destructed. Don't call Stop() here,
+ // destruction calls Stop() and doing so twice is illegal (null pointer
+ // dereference).
+ session_state_->arcore_gl_thread_ = nullptr;
+}
+
+void ArCoreDevice::RequestSession(
+ mojom::XRRuntimeSessionOptionsPtr options,
+ mojom::XRRuntime::RequestSessionCallback callback) {
+ DVLOG(1) << __func__;
+ DCHECK(IsOnMainThread());
+
+ if (HasExclusiveSession()) {
+ DVLOG(1) << __func__ << ": Rejecting additional session request";
+ std::move(callback).Run(nullptr, mojo::NullRemote());
+ return;
+ }
+
+ // Set HasExclusiveSession status to true. This lasts until OnSessionEnded.
+ OnStartPresenting();
+
+ DCHECK(!session_state_->pending_request_session_callback_);
+ session_state_->pending_request_session_callback_ = std::move(callback);
+ session_state_->enabled_features_ = options->enabled_features;
+
+ bool use_dom_overlay = base::Contains(
+ options->enabled_features, device::mojom::XRSessionFeature::DOM_OVERLAY);
+
+ // mailbox_bridge_ is either supplied from the constructor, or recreated in
+ // OnSessionEnded().
+ DCHECK(mailbox_bridge_);
+
+ session_state_->arcore_gl_thread_ = std::make_unique<ArCoreGlThread>(
+ std::move(ar_image_transport_factory_), std::move(mailbox_bridge_),
+ CreateMainThreadCallback(
+ base::BindOnce(&ArCoreDevice::OnGlThreadReady, GetWeakPtr(),
+ options->render_process_id, options->render_frame_id,
+ use_dom_overlay)));
+ session_state_->arcore_gl_thread_->Start();
+}
+
+void ArCoreDevice::OnGlThreadReady(int render_process_id,
+ int render_frame_id,
+ bool use_overlay) {
+ auto ready_callback =
+ base::BindRepeating(&ArCoreDevice::OnDrawingSurfaceReady, GetWeakPtr());
+ auto touch_callback =
+ base::BindRepeating(&ArCoreDevice::OnDrawingSurfaceTouch, GetWeakPtr());
+ auto destroyed_callback =
+ base::BindOnce(&ArCoreDevice::OnDrawingSurfaceDestroyed, GetWeakPtr());
+
+ arcore_session_utils_->RequestArSession(
+ render_process_id, render_frame_id, use_overlay,
+ std::move(ready_callback), std::move(touch_callback),
+ std::move(destroyed_callback));
+}
+
+void ArCoreDevice::OnDrawingSurfaceReady(gfx::AcceleratedWidget window,
+ display::Display::Rotation rotation,
+ const gfx::Size& frame_size) {
+ DVLOG(1) << __func__ << ": size=" << frame_size.width() << "x"
+ << frame_size.height() << " rotation=" << static_cast<int>(rotation);
+ DCHECK(!session_state_->is_arcore_gl_initialized_);
+
+ auto display_info = CreateVRDisplayInfo(frame_size);
+ SetVRDisplayInfo(std::move(display_info));
+
+ RequestArCoreGlInitialization(window, rotation, frame_size);
+}
+
+void ArCoreDevice::OnDrawingSurfaceTouch(bool is_primary,
+ bool touching,
+ int32_t pointer_id,
+ const gfx::PointF& location) {
+ DVLOG(2) << __func__ << ": pointer_id=" << pointer_id
+ << " is_primary=" << is_primary << " touching=" << touching;
+
+ if (!session_state_->is_arcore_gl_initialized_ ||
+ !session_state_->arcore_gl_thread_)
+ return;
+
+ PostTaskToGlThread(base::BindOnce(
+ &ArCoreGl::OnScreenTouch,
+ session_state_->arcore_gl_thread_->GetArCoreGl()->GetWeakPtr(),
+ is_primary, touching, pointer_id, location));
+}
+
+void ArCoreDevice::OnDrawingSurfaceDestroyed() {
+ DVLOG(1) << __func__;
+
+ CallDeferredRequestSessionCallback(/*success=*/false);
+
+ OnSessionEnded();
+}
+
+void ArCoreDevice::ShutdownSession(
+ mojom::XRRuntime::ShutdownSessionCallback on_completed) {
+ DVLOG(2) << __func__;
+ OnDrawingSurfaceDestroyed();
+ std::move(on_completed).Run();
+}
+
+void ArCoreDevice::OnSessionEnded() {
+ DVLOG(1) << __func__;
+
+ if (!HasExclusiveSession())
+ return;
+
+ // This may be a no-op in case session end was initiated from the Java side.
+ arcore_session_utils_->EndSession();
+
+ // The GL thread had initialized its context with a drawing_widget based on
+ // the ArImmersiveOverlay's Surface, and the one it has is no longer valid.
+ // For now, just destroy the GL thread so that it is recreated for the next
+ // session with fresh associated resources. Also go through these steps in
+ // case the GL thread hadn't completed, or had initialized partially, to
+ // ensure consistent state.
+
+ // TODO(https://crbug.com/849568): Instead of splitting the initialization
+ // of this class between construction and RequestSession, perform all the
+ // initialization at once on the first successful RequestSession call.
+
+ // Reset per-session members to initial values.
+ session_state_ = std::make_unique<ArCoreDevice::SessionState>();
+
+ // The image transport factory should be reusable, but we've std::moved it
+ // to the GL thread. Make a new one for next time. (This is cheap, it's
+ // just a factory.)
+ ar_image_transport_factory_ = std::make_unique<ArImageTransportFactory>();
+
+ // Create a new mailbox bridge for use in the next session. (This is cheap,
+ // the constructor doesn't establish a GL context.)
+ mailbox_bridge_ = mailbox_bridge_factory_->Create();
+
+ // This sets HasExclusiveSession status to false.
+ OnExitPresent();
+}
+
+void ArCoreDevice::CallDeferredRequestSessionCallback(bool success) {
+ DVLOG(1) << __func__ << " success=" << success;
+ DCHECK(IsOnMainThread());
+
+ // We might not have any pending session requests, i.e. if destroyed
+ // immediately after construction.
+ if (!session_state_->pending_request_session_callback_)
+ return;
+
+ mojom::XRRuntime::RequestSessionCallback deferred_callback =
+ std::move(session_state_->pending_request_session_callback_);
+
+ if (!success) {
+ std::move(deferred_callback).Run(nullptr, mojo::NullRemote());
+ return;
+ }
+
+ // Success case should only happen after GL thread is ready.
+ auto create_callback =
+ base::BindOnce(&ArCoreDevice::OnCreateSessionCallback, GetWeakPtr(),
+ std::move(deferred_callback));
+
+ auto shutdown_callback =
+ base::BindOnce(&ArCoreDevice::OnSessionEnded, GetWeakPtr());
+
+ PostTaskToGlThread(base::BindOnce(
+ &ArCoreGl::CreateSession,
+ session_state_->arcore_gl_thread_->GetArCoreGl()->GetWeakPtr(),
+ display_info_->Clone(),
+ CreateMainThreadCallback(std::move(create_callback)),
+ CreateMainThreadCallback(std::move(shutdown_callback))));
+}
+
+void ArCoreDevice::OnCreateSessionCallback(
+ mojom::XRRuntime::RequestSessionCallback deferred_callback,
+ mojo::PendingRemote<mojom::XRFrameDataProvider> frame_data_provider,
+ mojom::VRDisplayInfoPtr display_info,
+ mojo::PendingRemote<mojom::XRSessionController> session_controller,
+ mojom::XRPresentationConnectionPtr presentation_connection) {
+ DVLOG(2) << __func__;
+ DCHECK(IsOnMainThread());
+
+ mojom::XRSessionPtr session = mojom::XRSession::New();
+ session->data_provider = std::move(frame_data_provider);
+ session->display_info = std::move(display_info);
+ session->submit_frame_sink = std::move(presentation_connection);
+
+ std::move(deferred_callback)
+ .Run(std::move(session), std::move(session_controller));
+}
+
+void ArCoreDevice::PostTaskToGlThread(base::OnceClosure task) {
+ DCHECK(IsOnMainThread());
+ session_state_->arcore_gl_thread_->GetArCoreGl()
+ ->GetGlThreadTaskRunner()
+ ->PostTask(FROM_HERE, std::move(task));
+}
+
+bool ArCoreDevice::IsOnMainThread() {
+ return main_thread_task_runner_->BelongsToCurrentThread();
+}
+
+void ArCoreDevice::RequestArCoreGlInitialization(
+ gfx::AcceleratedWidget drawing_widget,
+ int drawing_rotation,
+ const gfx::Size& frame_size) {
+ DVLOG(1) << __func__;
+ DCHECK(IsOnMainThread());
+
+ if (!arcore_session_utils_->EnsureLoaded()) {
+ DLOG(ERROR) << "ARCore was not loaded properly.";
+ OnArCoreGlInitializationComplete(false);
+ return;
+ }
+
+ if (!session_state_->is_arcore_gl_initialized_) {
+ // We will only try to initialize ArCoreGl once, at the end of the
+ // permission sequence, and will resolve pending requests that have queued
+ // up once that initialization completes. We set is_arcore_gl_initialized_
+ // in the callback to block operations that require it to be ready.
+ auto rotation = static_cast<display::Display::Rotation>(drawing_rotation);
+ PostTaskToGlThread(base::BindOnce(
+ &ArCoreGl::Initialize,
+ session_state_->arcore_gl_thread_->GetArCoreGl()->GetWeakPtr(),
+ arcore_session_utils_.get(), arcore_factory_.get(), drawing_widget,
+ frame_size, rotation, session_state_->enabled_features_,
+ CreateMainThreadCallback(base::BindOnce(
+ &ArCoreDevice::OnArCoreGlInitializationComplete, GetWeakPtr()))));
+ return;
+ }
+
+ OnArCoreGlInitializationComplete(true);
+}
+
+void ArCoreDevice::OnArCoreGlInitializationComplete(bool success) {
+ DVLOG(1) << __func__;
+ DCHECK(IsOnMainThread());
+
+ if (!success) {
+ CallDeferredRequestSessionCallback(/*success=*/false);
+ return;
+ }
+
+ session_state_->is_arcore_gl_initialized_ = true;
+
+ // We only start GL initialization after the user has granted consent, so we
+ // can now start the session.
+ CallDeferredRequestSessionCallback(/*success=*/true);
+}
+
+} // namespace device
diff --git a/chromium/device/vr/android/arcore/arcore_device.h b/chromium/device/vr/android/arcore/arcore_device.h
new file mode 100644
index 00000000000..1e9858e16ef
--- /dev/null
+++ b/chromium/device/vr/android/arcore/arcore_device.h
@@ -0,0 +1,155 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_VR_ANDROID_ARCORE_ARCORE_DEVICE_H_
+#define DEVICE_VR_ANDROID_ARCORE_ARCORE_DEVICE_H_
+
+#include <jni.h>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/android/jni_android.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "device/vr/vr_device.h"
+#include "device/vr/vr_device_base.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "ui/gfx/geometry/size_f.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace vr {
+class ArCoreSessionUtils;
+} // namespace vr
+
+namespace device {
+
+class ArImageTransportFactory;
+class ArCoreFactory;
+class ArCoreGlThread;
+class MailboxToSurfaceBridge;
+class MailboxToSurfaceBridgeFactory;
+
+class COMPONENT_EXPORT(VR_ARCORE) ArCoreDevice : public VRDeviceBase {
+ public:
+ ArCoreDevice(
+ std::unique_ptr<ArCoreFactory> arcore_factory,
+ std::unique_ptr<ArImageTransportFactory> ar_image_transport_factory,
+ std::unique_ptr<MailboxToSurfaceBridgeFactory>
+ mailbox_to_surface_bridge_factory,
+ std::unique_ptr<vr::ArCoreSessionUtils> arcore_session_utils);
+ ~ArCoreDevice() override;
+
+ // VRDeviceBase implementation.
+ void RequestSession(
+ mojom::XRRuntimeSessionOptionsPtr options,
+ mojom::XRRuntime::RequestSessionCallback callback) override;
+
+ void ShutdownSession(mojom::XRRuntime::ShutdownSessionCallback) override;
+
+ base::WeakPtr<ArCoreDevice> GetWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+ }
+
+ private:
+ void OnDrawingSurfaceReady(gfx::AcceleratedWidget window,
+ display::Display::Rotation rotation,
+ const gfx::Size& frame_size);
+ void OnDrawingSurfaceTouch(bool is_primary,
+ bool touching,
+ int32_t pointer_id,
+ const gfx::PointF& location);
+ void OnDrawingSurfaceDestroyed();
+ void OnSessionEnded();
+
+ template <typename... Args>
+ static void RunCallbackOnTaskRunner(
+ const scoped_refptr<base::TaskRunner>& task_runner,
+ base::OnceCallback<void(Args...)> callback,
+ Args... args) {
+ task_runner->PostTask(
+ FROM_HERE,
+ base::BindOnce(std::move(callback), std::forward<Args>(args)...));
+ }
+ template <typename... Args>
+ base::OnceCallback<void(Args...)> CreateMainThreadCallback(
+ base::OnceCallback<void(Args...)> callback) {
+ return base::BindOnce(&ArCoreDevice::RunCallbackOnTaskRunner<Args...>,
+ main_thread_task_runner_, std::move(callback));
+ }
+
+ void PostTaskToGlThread(base::OnceClosure task);
+
+ bool IsOnMainThread();
+
+ // Called once the GL thread is started. At this point, it doesn't
+ // have a valid GL context yet.
+ void OnGlThreadReady(int render_process_id,
+ int render_frame_id,
+ bool use_overlay);
+
+ // Replies to the pending mojo RequestSession request.
+ void CallDeferredRequestSessionCallback(bool success);
+
+ // Tells the GL thread to initialize a GL context and other resources,
+ // using the supplied window as a drawing surface.
+ void RequestArCoreGlInitialization(gfx::AcceleratedWidget window,
+ int rotation,
+ const gfx::Size& size);
+
+ // Called when the GL thread's GL context initialization completes.
+ void OnArCoreGlInitializationComplete(bool success);
+
+ void OnCreateSessionCallback(
+ mojom::XRRuntime::RequestSessionCallback deferred_callback,
+ mojo::PendingRemote<mojom::XRFrameDataProvider> frame_data_provider,
+ mojom::VRDisplayInfoPtr display_info,
+ mojo::PendingRemote<mojom::XRSessionController> session_controller,
+ mojom::XRPresentationConnectionPtr presentation_connection);
+
+ scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_;
+ std::unique_ptr<ArCoreFactory> arcore_factory_;
+ std::unique_ptr<ArImageTransportFactory> ar_image_transport_factory_;
+ std::unique_ptr<MailboxToSurfaceBridgeFactory> mailbox_bridge_factory_;
+ std::unique_ptr<vr::ArCoreSessionUtils> arcore_session_utils_;
+
+ std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge_;
+
+ // Encapsulates data with session lifetime.
+ struct SessionState {
+ SessionState();
+ ~SessionState();
+
+ std::unique_ptr<ArCoreGlThread> arcore_gl_thread_;
+ bool is_arcore_gl_initialized_ = false;
+
+ base::OnceClosure start_immersive_activity_callback_;
+
+ // The initial requestSession triggers the initialization sequence, store
+ // the callback for replying once that initialization completes. Only one
+ // concurrent session is supported, other requests are rejected.
+ mojom::XRRuntime::RequestSessionCallback pending_request_session_callback_;
+
+ // List of features that are enabled on the session.
+ std::vector<device::mojom::XRSessionFeature> enabled_features_;
+ };
+
+ // This object is reset to initial values when ending a session. This helps
+ // ensure that each session has consistent per-session state.
+ std::unique_ptr<SessionState> session_state_;
+
+ base::OnceCallback<void(bool)>
+ on_request_arcore_install_or_update_result_callback_;
+ base::OnceCallback<void(bool)> on_request_ar_module_result_callback_;
+
+ // Must be last.
+ base::WeakPtrFactory<ArCoreDevice> weak_ptr_factory_{this};
+ DISALLOW_COPY_AND_ASSIGN(ArCoreDevice);
+};
+
+} // namespace device
+
+#endif // DEVICE_VR_ANDROID_ARCORE_ARCORE_DEVICE_H_
diff --git a/chromium/device/vr/android/arcore/arcore_gl.cc b/chromium/device/vr/android/arcore/arcore_gl.cc
new file mode 100644
index 00000000000..81beaef2471
--- /dev/null
+++ b/chromium/device/vr/android/arcore/arcore_gl.cc
@@ -0,0 +1,1110 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/vr/android/arcore/arcore_gl.h"
+
+#include <algorithm>
+#include <iomanip>
+#include <limits>
+#include <utility>
+#include "base/android/android_hardware_buffer_compat.h"
+#include "base/android/jni_android.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/containers/queue.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/task/post_task.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "base/trace_event/trace_event.h"
+#include "base/trace_event/traced_value.h"
+#include "device/vr/android/arcore/ar_image_transport.h"
+#include "device/vr/android/arcore/arcore.h"
+#include "device/vr/android/arcore/arcore_math_utils.h"
+#include "device/vr/android/arcore/arcore_session_utils.h"
+#include "device/vr/android/arcore/type_converters.h"
+#include "device/vr/android/web_xr_presentation_state.h"
+#include "device/vr/public/mojom/pose.h"
+#include "device/vr/public/mojom/vr_service.mojom.h"
+#include "gpu/ipc/common/gpu_memory_buffer_impl_android_hardware_buffer.h"
+#include "ui/display/display.h"
+#include "ui/display/screen.h"
+#include "ui/gfx/geometry/angle_conversions.h"
+#include "ui/gfx/gpu_fence.h"
+#include "ui/gl/android/scoped_java_surface.h"
+#include "ui/gl/android/surface_texture.h"
+#include "ui/gl/gl_bindings.h"
+#include "ui/gl/gl_context.h"
+#include "ui/gl/gl_fence_egl.h"
+#include "ui/gl/gl_image_ahardwarebuffer.h"
+#include "ui/gl/gl_surface.h"
+#include "ui/gl/init/gl_factory.h"
+
+namespace {
+// When scheduling the next ARCore update task, aim to have that run this much
+// time ahead of when the next camera image is expected to be ready. In case
+// the overall system is running slower than ideal, i.e. if the device switches
+// from 30fps to 60fps, it'll catch up by this amount every frame until it
+// reaches a new steady state.
+constexpr base::TimeDelta kUpdateTargetDelta =
+ base::TimeDelta::FromMilliseconds(2);
+
+// Maximum delay for scheduling the next ARCore update. This helps ensure
+// that there isn't an unreasonable delay due to a bogus estimate if the device
+// is paused or unresponsive.
+constexpr base::TimeDelta kUpdateMaxDelay =
+ base::TimeDelta::FromMilliseconds(30);
+
+const char kInputSourceProfileName[] = "generic-touchscreen";
+
+const gfx::Size kDefaultFrameSize = {1, 1};
+const display::Display::Rotation kDefaultRotation = display::Display::ROTATE_0;
+
+} // namespace
+
+namespace device {
+
+ArCoreGl::ArCoreGl(std::unique_ptr<ArImageTransport> ar_image_transport)
+ : gl_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
+ ar_image_transport_(std::move(ar_image_transport)),
+ webxr_(std::make_unique<vr::WebXrPresentationState>()) {
+ DVLOG(1) << __func__;
+}
+
+ArCoreGl::~ArCoreGl() {
+ DVLOG(1) << __func__;
+ DCHECK(IsOnGlThread());
+ ar_image_transport_->DestroySharedBuffers(webxr_.get());
+ ar_image_transport_.reset();
+
+ // Make sure mojo bindings are closed before proceeding with member
+ // destruction. Specifically, destroying pending_getframedata_
+ // must happen after closing bindings, see RunNextGetFrameData()
+ // comments.
+ CloseBindingsIfOpen();
+}
+
+void ArCoreGl::Initialize(
+ vr::ArCoreSessionUtils* session_utils,
+ ArCoreFactory* arcore_factory,
+ gfx::AcceleratedWidget drawing_widget,
+ const gfx::Size& frame_size,
+ display::Display::Rotation display_rotation,
+ const std::vector<device::mojom::XRSessionFeature>& enabled_features,
+ base::OnceCallback<void(bool)> callback) {
+ DVLOG(3) << __func__;
+
+ DCHECK(IsOnGlThread());
+ DCHECK(!is_initialized_);
+
+ transfer_size_ = frame_size;
+ camera_image_size_ = frame_size;
+ display_rotation_ = display_rotation;
+ // TODO(https://crbug.com/953503): start using the list to control the
+ // behavior of local and unbounded spaces & send appropriate data back in
+ // GetFrameData().
+ enabled_features_.insert(enabled_features.begin(), enabled_features.end());
+ should_update_display_geometry_ = true;
+
+ if (!InitializeGl(drawing_widget)) {
+ std::move(callback).Run(false);
+ return;
+ }
+
+ // Get the activity context.
+ base::android::ScopedJavaLocalRef<jobject> application_context =
+ session_utils->GetApplicationContext();
+ if (!application_context.obj()) {
+ DLOG(ERROR) << "Unable to retrieve the Java context/activity!";
+ std::move(callback).Run(false);
+ return;
+ }
+
+ arcore_ = arcore_factory->Create();
+ if (!arcore_->Initialize(application_context, enabled_features_)) {
+ DLOG(ERROR) << "ARCore failed to initialize";
+ std::move(callback).Run(false);
+ return;
+ }
+
+ DVLOG(3) << "ar_image_transport_->Initialize()...";
+ ar_image_transport_->Initialize(
+ webxr_.get(),
+ base::BindOnce(&ArCoreGl::OnArImageTransportReady,
+ weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+
+ // Set the texture on ArCore to render the camera. Must be after
+ // ar_image_transport_->Initialize().
+ arcore_->SetCameraTexture(ar_image_transport_->GetCameraTextureId());
+ // Set the Geometry to ensure consistent behaviour.
+ arcore_->SetDisplayGeometry(kDefaultFrameSize, kDefaultRotation);
+}
+
+void ArCoreGl::OnArImageTransportReady(
+ base::OnceCallback<void(bool)> callback) {
+ DVLOG(3) << __func__;
+ is_initialized_ = true;
+ webxr_->NotifyMailboxBridgeReady();
+ std::move(callback).Run(true);
+}
+
+void ArCoreGl::CreateSession(mojom::VRDisplayInfoPtr display_info,
+ ArCoreGlCreateSessionCallback create_callback,
+ base::OnceClosure shutdown_callback) {
+ DVLOG(3) << __func__;
+
+ DCHECK(IsOnGlThread());
+ DCHECK(is_initialized_);
+
+ session_shutdown_callback_ = std::move(shutdown_callback);
+
+ CloseBindingsIfOpen();
+
+ device::mojom::XRPresentationTransportOptionsPtr transport_options =
+ device::mojom::XRPresentationTransportOptions::New();
+ transport_options->wait_for_gpu_fence = true;
+
+ if (ar_image_transport_->UseSharedBuffer()) {
+ DVLOG(2) << __func__
+ << ": UseSharedBuffer()=true, DRAW_INTO_TEXTURE_MAILBOX";
+ transport_options->transport_method =
+ device::mojom::XRPresentationTransportMethod::DRAW_INTO_TEXTURE_MAILBOX;
+ } else {
+ DVLOG(2) << __func__
+ << ": UseSharedBuffer()=false, SUBMIT_AS_MAILBOX_HOLDER";
+ transport_options->transport_method =
+ device::mojom::XRPresentationTransportMethod::SUBMIT_AS_MAILBOX_HOLDER;
+ transport_options->wait_for_transfer_notification = true;
+ ar_image_transport_->SetFrameAvailableCallback(base::BindRepeating(
+ &ArCoreGl::OnTransportFrameAvailable, weak_ptr_factory_.GetWeakPtr()));
+ }
+
+ auto submit_frame_sink = device::mojom::XRPresentationConnection::New();
+ submit_frame_sink->client_receiver =
+ submit_client_.BindNewPipeAndPassReceiver();
+ submit_frame_sink->provider =
+ presentation_receiver_.BindNewPipeAndPassRemote();
+ submit_frame_sink->transport_options = std::move(transport_options);
+
+ display_info_ = std::move(display_info);
+
+ std::move(create_callback)
+ .Run(frame_data_receiver_.BindNewPipeAndPassRemote(),
+ display_info_->Clone(),
+ session_controller_receiver_.BindNewPipeAndPassRemote(),
+ std::move(submit_frame_sink));
+
+ frame_data_receiver_.set_disconnect_handler(base::BindOnce(
+ &ArCoreGl::OnBindingDisconnect, weak_ptr_factory_.GetWeakPtr()));
+ session_controller_receiver_.set_disconnect_handler(base::BindOnce(
+ &ArCoreGl::OnBindingDisconnect, weak_ptr_factory_.GetWeakPtr()));
+}
+
+bool ArCoreGl::InitializeGl(gfx::AcceleratedWidget drawing_widget) {
+ DVLOG(3) << __func__;
+
+ DCHECK(IsOnGlThread());
+ DCHECK(!is_initialized_);
+
+ if (gl::GetGLImplementation() == gl::kGLImplementationNone &&
+ !gl::init::InitializeGLOneOff()) {
+ DLOG(ERROR) << "gl::init::InitializeGLOneOff failed";
+ return false;
+ }
+
+ scoped_refptr<gl::GLSurface> surface =
+ gl::init::CreateViewGLSurface(drawing_widget);
+ DVLOG(3) << "surface=" << surface.get();
+ if (!surface.get()) {
+ DLOG(ERROR) << "gl::init::CreateViewGLSurface failed";
+ return false;
+ }
+
+ gl::GLContextAttribs context_attribs;
+ // When using augmented images or certain other ARCore features that involve a
+ // frame delay, ARCore's shared EGL context needs to be compatible with ours.
+ // Any mismatches result in a EGL_BAD_MATCH error, including different reset
+ // notification behavior according to
+ // https://www.khronos.org/registry/EGL/specs/eglspec.1.5.pdf page 56.
+ // Chromium defaults to lose context on reset when the robustness extension is
+ // present, even if robustness features are not requested specifically.
+ context_attribs.client_major_es_version = 3;
+ context_attribs.client_minor_es_version = 0;
+ context_attribs.lose_context_on_reset = false;
+
+ scoped_refptr<gl::GLContext> context =
+ gl::init::CreateGLContext(nullptr, surface.get(), context_attribs);
+ if (!context.get()) {
+ DLOG(ERROR) << "gl::init::CreateGLContext failed";
+ return false;
+ }
+ if (!context->MakeCurrent(surface.get())) {
+ DLOG(ERROR) << "gl::GLContext::MakeCurrent() failed";
+ return false;
+ }
+
+ // Assign the surface and context members now that initialization has
+ // succeeded.
+ surface_ = std::move(surface);
+ context_ = std::move(context);
+
+ DVLOG(3) << "done";
+ return true;
+}
+
+void ArCoreGl::GetFrameData(
+ mojom::XRFrameDataRequestOptionsPtr options,
+ mojom::XRFrameDataProvider::GetFrameDataCallback callback) {
+ TRACE_EVENT0("gpu", __func__);
+
+ if (webxr_->HaveAnimatingFrame()) {
+ DVLOG(3) << __func__ << ": deferring, HaveAnimatingFrame";
+ pending_getframedata_ =
+ base::BindOnce(&ArCoreGl::GetFrameData, GetWeakPtr(),
+ std::move(options), std::move(callback));
+ return;
+ }
+
+ DVLOG(3) << __func__ << ": should_update_display_geometry_="
+ << should_update_display_geometry_
+ << ", transfer_size_=" << transfer_size_.ToString()
+ << ", display_rotation_=" << display_rotation_;
+
+ DCHECK(IsOnGlThread());
+ DCHECK(is_initialized_);
+
+ if (restrict_frame_data_) {
+ std::move(callback).Run(nullptr);
+ return;
+ }
+
+ if (is_paused_) {
+ DVLOG(2) << __func__ << ": paused but frame data not restricted. Resuming.";
+ Resume();
+ }
+
+ // Check if the frame_size and display_rotation updated last frame. If yes,
+ // apply the update for this frame. In the current implementation, this should
+ // only happen once per session since we don't support mid-session rotation or
+ // resize.
+ if (should_recalculate_uvs_) {
+ // Get the UV transform matrix from ArCore's UV transform.
+ uv_transform_ = arcore_->GetCameraUvFromScreenUvTransform();
+
+ DVLOG(3) << __func__ << ": uv_transform_=" << uv_transform_.ToString();
+
+ // We need near/far distances to make a projection matrix. The actual
+ // values don't matter, the Renderer will recalculate dependent values
+ // based on the application's near/far settngs.
+ constexpr float depth_near = 0.1f;
+ constexpr float depth_far = 1000.f;
+ projection_ = arcore_->GetProjectionMatrix(depth_near, depth_far);
+ auto m = projection_.matrix();
+ float left = depth_near * (m.get(2, 0) - 1.f) / m.get(0, 0);
+ float right = depth_near * (m.get(2, 0) + 1.f) / m.get(0, 0);
+ float bottom = depth_near * (m.get(2, 1) - 1.f) / m.get(1, 1);
+ float top = depth_near * (m.get(2, 1) + 1.f) / m.get(1, 1);
+
+ // Also calculate the inverse projection which is needed for converting
+ // screen touches to world rays.
+ bool has_inverse = projection_.GetInverse(&inverse_projection_);
+ DCHECK(has_inverse);
+
+ // VRFieldOfView wants positive angles.
+ mojom::VRFieldOfViewPtr field_of_view = mojom::VRFieldOfView::New();
+ field_of_view->left_degrees = gfx::RadToDeg(atanf(-left / depth_near));
+ field_of_view->right_degrees = gfx::RadToDeg(atanf(right / depth_near));
+ field_of_view->down_degrees = gfx::RadToDeg(atanf(-bottom / depth_near));
+ field_of_view->up_degrees = gfx::RadToDeg(atanf(top / depth_near));
+ DVLOG(3) << " fov degrees up=" << field_of_view->up_degrees
+ << " down=" << field_of_view->down_degrees
+ << " left=" << field_of_view->left_degrees
+ << " right=" << field_of_view->right_degrees;
+
+ display_info_->left_eye->field_of_view = std::move(field_of_view);
+ display_info_changed_ = true;
+
+ should_recalculate_uvs_ = false;
+ }
+
+ // Now check if the frame_size or display_rotation needs to be updated
+ // for the next frame. This must happen after the should_recalculate_uvs_
+ // check above to ensure it executes with the needed one-frame delay.
+ // The delay is needed due to the fact that ArCoreImpl already got a frame
+ // and we don't want to calculate uvs for stale frame with new geometry.
+ if (should_update_display_geometry_) {
+ // Set display geometry before calling Update. It's a pending request that
+ // applies to the next frame.
+ arcore_->SetDisplayGeometry(camera_image_size_, display_rotation_);
+
+ // Tell the uvs to recalculate on the next animation frame, by which time
+ // SetDisplayGeometry will have set the new values in arcore_.
+ should_recalculate_uvs_ = true;
+ should_update_display_geometry_ = false;
+ }
+
+ bool camera_updated = false;
+ base::TimeTicks arcore_update_started = base::TimeTicks::Now();
+ mojom::VRPosePtr pose = arcore_->Update(&camera_updated);
+ base::TimeTicks now = base::TimeTicks::Now();
+ base::TimeDelta frame_timestamp = arcore_->GetFrameTimestamp();
+
+ DVLOG(3) << __func__ << ": frame_timestamp=" << frame_timestamp;
+
+ if (!arcore_last_frame_timestamp_.is_zero()) {
+ arcore_frame_interval_ = frame_timestamp - arcore_last_frame_timestamp_;
+ arcore_update_next_expected_ = now + arcore_frame_interval_;
+ }
+ arcore_last_frame_timestamp_ = frame_timestamp;
+ base::TimeDelta arcore_update_elapsed = now - arcore_update_started;
+ TRACE_COUNTER1("gpu", "ARCore update elapsed (ms)",
+ arcore_update_elapsed.InMilliseconds());
+
+ if (!camera_updated) {
+ DVLOG(1) << "arcore_->Update() failed";
+ std::move(callback).Run(nullptr);
+ have_camera_image_ = false;
+ return;
+ }
+
+ // First frame will be requested without a prior call to SetDisplayGeometry -
+ // handle this case.
+ if (transfer_size_.IsEmpty()) {
+ DLOG(ERROR) << "No valid AR frame size provided!";
+ std::move(callback).Run(nullptr);
+ have_camera_image_ = false;
+ return;
+ }
+
+ have_camera_image_ = true;
+ mojom::XRFrameDataPtr frame_data = mojom::XRFrameData::New();
+
+ // Check if floor height estimate has changed.
+ float new_floor_height_estimate = arcore_->GetEstimatedFloorHeight();
+ if (!floor_height_estimate_ ||
+ *floor_height_estimate_ != new_floor_height_estimate) {
+ floor_height_estimate_ = new_floor_height_estimate;
+
+ frame_data->stage_parameters_updated = true;
+ frame_data->stage_parameters = mojom::VRStageParameters::New();
+ frame_data->stage_parameters->mojo_from_floor = gfx::Transform();
+ frame_data->stage_parameters->mojo_from_floor.Translate3d(
+ 0, (-1 * *floor_height_estimate_), 0);
+ }
+
+ frame_data->frame_id = webxr_->StartFrameAnimating();
+ DVLOG(2) << __func__ << " frame=" << frame_data->frame_id;
+ TRACE_EVENT1("gpu", __func__, "frame", frame_data->frame_id);
+
+ vr::WebXrFrame* xrframe = webxr_->GetAnimatingFrame();
+ xrframe->time_pose = now;
+
+ if (display_info_changed_) {
+ frame_data->left_eye = display_info_->left_eye.Clone();
+ display_info_changed_ = false;
+ }
+
+ if (ar_image_transport_->UseSharedBuffer()) {
+ // Set up a shared buffer for the renderer to draw into, it'll be sent
+ // alongside the frame pose.
+ gpu::MailboxHolder buffer_holder = ar_image_transport_->TransferFrame(
+ webxr_.get(), transfer_size_, uv_transform_);
+ frame_data->buffer_holder = buffer_holder;
+
+ if (IsFeatureEnabled(device::mojom::XRSessionFeature::CAMERA_ACCESS)) {
+ gpu::MailboxHolder camera_image_buffer_holder =
+ ar_image_transport_->TransferCameraImageFrame(
+ webxr_.get(), transfer_size_, uv_transform_);
+ frame_data->camera_image_buffer_holder = camera_image_buffer_holder;
+ }
+ }
+
+ // Create the frame data to return to the renderer.
+ if (!pose) {
+ DVLOG(1) << __func__ << ": pose unavailable!";
+ }
+
+ frame_data->pose = std::move(pose);
+ frame_data->time_delta = now - base::TimeTicks();
+
+ fps_meter_.AddFrame(now);
+ TRACE_COUNTER1("gpu", "WebXR FPS", fps_meter_.GetFPS());
+
+ // Post a task to finish processing the frame to give a chance for
+ // OnScreenTouch() tasks to run and added anchors to be registered.
+ gl_thread_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&ArCoreGl::ProcessFrame, weak_ptr_factory_.GetWeakPtr(),
+ std::move(options), std::move(frame_data),
+ std::move(callback)));
+}
+
+bool ArCoreGl::IsSubmitFrameExpected(int16_t frame_index) {
+ // submit_client_ could be null when we exit presentation, if there were
+ // pending SubmitFrame messages queued. XRSessionClient::OnExitPresent
+ // will clean up state in blink, so it doesn't wait for
+ // OnSubmitFrameTransferred or OnSubmitFrameRendered. Similarly,
+ // the animating frame state is cleared when exiting presentation,
+ // and we should ignore a leftover queued SubmitFrame.
+ if (!submit_client_.get() || !webxr_->HaveAnimatingFrame())
+ return false;
+
+ vr::WebXrFrame* animating_frame = webxr_->GetAnimatingFrame();
+ animating_frame->time_js_submit = base::TimeTicks::Now();
+
+ if (animating_frame->index != frame_index) {
+ DVLOG(1) << __func__ << ": wrong frame index, got " << frame_index
+ << ", expected " << animating_frame->index;
+ mojo::ReportBadMessage("SubmitFrame called with wrong frame index");
+ CloseBindingsIfOpen();
+ return false;
+ }
+
+ // Frame looks valid.
+ return true;
+}
+
+void ArCoreGl::CopyCameraImageToFramebuffer() {
+ DVLOG(2) << __func__;
+
+ // Draw the current camera texture to the output default framebuffer now, if
+ // available.
+ if (have_camera_image_) {
+ glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, 0);
+ ar_image_transport_->CopyCameraImageToFramebuffer(camera_image_size_,
+ uv_transform_);
+ have_camera_image_ = false;
+ }
+
+ // We're done with the camera image for this frame, post a task to start the
+ // next ARCore update if we had deferred it. This will get the next frame's
+ // camera image and pose in parallel while we're waiting for this frame's
+ // rendered image.
+ if (pending_getframedata_) {
+ base::TimeDelta delay = base::TimeDelta();
+ if (!arcore_update_next_expected_.is_null()) {
+ // Try to schedule the next ARCore update to happen a short time before
+ // the camera image is expected to be ready..
+ delay = arcore_update_next_expected_ - base::TimeTicks::Now() -
+ kUpdateTargetDelta;
+ if (delay < base::TimeDelta()) {
+ // Negative sleep means we're behind schedule, run immediately.
+ delay = base::TimeDelta();
+ } else {
+ if (delay > kUpdateMaxDelay) {
+ DVLOG(1) << __func__ << ": delay " << delay << " too long, clamp to "
+ << kUpdateMaxDelay;
+ delay = kUpdateMaxDelay;
+ }
+ }
+ }
+ TRACE_COUNTER1("gpu", "ARCore update schedule (ms)",
+ delay.InMilliseconds());
+ // RunNextGetFrameData is needed since we must retain ownership of the mojo
+ // callback inside the pending_getframedata_ closure.
+ gl_thread_task_runner_->PostDelayedTask(
+ FROM_HERE, base::BindOnce(&ArCoreGl::RunNextGetFrameData, GetWeakPtr()),
+ delay);
+ }
+}
+
+void ArCoreGl::RunNextGetFrameData() {
+ DVLOG(3) << __func__;
+ DCHECK(pending_getframedata_);
+ std::move(pending_getframedata_).Run();
+}
+
+void ArCoreGl::FinishFrame(int16_t frame_index) {
+ TRACE_EVENT1("gpu", __func__, "frame", frame_index);
+ DVLOG(3) << __func__;
+ surface_->SwapBuffers(base::DoNothing());
+
+ // If we have a rendering frame (we don't if the app didn't submit one),
+ // update statistics.
+ if (!webxr_->HaveRenderingFrame())
+ return;
+ vr::WebXrFrame* frame = webxr_->GetRenderingFrame();
+ base::TimeDelta pose_to_submit = frame->time_js_submit - frame->time_pose;
+ base::TimeDelta submit_to_swap =
+ base::TimeTicks::Now() - frame->time_js_submit;
+ TRACE_COUNTER2("gpu", "WebXR frame time (ms)", "javascript",
+ pose_to_submit.InMilliseconds(), "processing",
+ submit_to_swap.InMilliseconds());
+}
+
+void ArCoreGl::SubmitFrameMissing(int16_t frame_index,
+ const gpu::SyncToken& sync_token) {
+ TRACE_EVENT1("gpu", __func__, "frame", frame_index);
+ DVLOG(2) << __func__;
+
+ if (!IsSubmitFrameExpected(frame_index))
+ return;
+
+ webxr_->RecycleUnusedAnimatingFrame();
+ ar_image_transport_->WaitSyncToken(sync_token);
+
+ CopyCameraImageToFramebuffer();
+
+ FinishFrame(frame_index);
+ DVLOG(3) << __func__ << ": frame=" << frame_index << " SwapBuffers";
+}
+
+void ArCoreGl::SubmitFrame(int16_t frame_index,
+ const gpu::MailboxHolder& mailbox,
+ base::TimeDelta time_waited) {
+ TRACE_EVENT1("gpu", __func__, "frame", frame_index);
+ DVLOG(2) << __func__ << ": frame=" << frame_index;
+ DCHECK(!ar_image_transport_->UseSharedBuffer());
+
+ if (!IsSubmitFrameExpected(frame_index))
+ return;
+
+ webxr_->ProcessOrDefer(base::BindOnce(&ArCoreGl::ProcessFrameFromMailbox,
+ weak_ptr_factory_.GetWeakPtr(),
+ frame_index, mailbox));
+}
+
+void ArCoreGl::ProcessFrameFromMailbox(int16_t frame_index,
+ const gpu::MailboxHolder& mailbox) {
+ TRACE_EVENT1("gpu", __func__, "frame", frame_index);
+ DVLOG(2) << __func__ << ": frame=" << frame_index;
+ DCHECK(webxr_->HaveProcessingFrame());
+ DCHECK(!ar_image_transport_->UseSharedBuffer());
+
+ ar_image_transport_->CopyMailboxToSurfaceAndSwap(transfer_size_, mailbox);
+ // Notify the client that we're done with the mailbox so that the underlying
+ // image is eligible for destruction.
+ submit_client_->OnSubmitFrameTransferred(true);
+
+ CopyCameraImageToFramebuffer();
+
+ // Now wait for ar_image_transport_ to call OnTransportFrameAvailable
+ // indicating that the image drawn onto the Surface is ready for consumption
+ // from the SurfaceTexture.
+}
+
+void ArCoreGl::OnTransportFrameAvailable(const gfx::Transform& uv_transform) {
+ DVLOG(2) << __func__;
+ DCHECK(!ar_image_transport_->UseSharedBuffer());
+ DCHECK(webxr_->HaveProcessingFrame());
+ int16_t frame_index = webxr_->GetProcessingFrame()->index;
+ TRACE_EVENT1("gpu", __func__, "frame", frame_index);
+ webxr_->GetProcessingFrame()->time_copied = base::TimeTicks::Now();
+ webxr_->TransitionFrameProcessingToRendering();
+
+ glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, 0);
+ ar_image_transport_->CopyDrawnImageToFramebuffer(
+ webxr_.get(), camera_image_size_, uv_transform);
+
+ FinishFrame(frame_index);
+
+ webxr_->EndFrameRendering();
+
+ if (submit_client_) {
+ // Create a local GpuFence and pass it to the Renderer via IPC.
+ std::unique_ptr<gl::GLFence> gl_fence = gl::GLFence::CreateForGpuFence();
+ std::unique_ptr<gfx::GpuFence> gpu_fence2 = gl_fence->GetGpuFence();
+ submit_client_->OnSubmitFrameGpuFence(
+ gpu_fence2->GetGpuFenceHandle().Clone());
+ }
+ // We finished processing a frame, unblock a potentially waiting next frame.
+ webxr_->TryDeferredProcessing();
+}
+
+void ArCoreGl::SubmitFrameWithTextureHandle(
+ int16_t frame_index,
+ mojo::PlatformHandle texture_handle) {
+ NOTIMPLEMENTED();
+}
+
+void ArCoreGl::SubmitFrameDrawnIntoTexture(int16_t frame_index,
+ const gpu::SyncToken& sync_token,
+ base::TimeDelta time_waited) {
+ TRACE_EVENT1("gpu", __func__, "frame", frame_index);
+ DVLOG(2) << __func__ << ": frame=" << frame_index;
+ DCHECK(ar_image_transport_->UseSharedBuffer());
+
+ if (!IsSubmitFrameExpected(frame_index))
+ return;
+
+ // Start processing the frame now if possible. If there's already a current
+ // processing frame, defer it until that frame calls TryDeferredProcessing.
+ webxr_->ProcessOrDefer(base::BindOnce(&ArCoreGl::ProcessFrameDrawnIntoTexture,
+ weak_ptr_factory_.GetWeakPtr(),
+ frame_index, sync_token));
+}
+
+void ArCoreGl::ProcessFrameDrawnIntoTexture(int16_t frame_index,
+ const gpu::SyncToken& sync_token) {
+ TRACE_EVENT1("gpu", __func__, "frame", frame_index);
+
+ DCHECK(webxr_->HaveProcessingFrame());
+ DCHECK(ar_image_transport_->UseSharedBuffer());
+ CopyCameraImageToFramebuffer();
+
+ ar_image_transport_->CreateGpuFenceForSyncToken(
+ sync_token, base::BindOnce(&ArCoreGl::OnWebXrTokenSignaled, GetWeakPtr(),
+ frame_index));
+}
+
+void ArCoreGl::OnWebXrTokenSignaled(int16_t frame_index,
+ std::unique_ptr<gfx::GpuFence> gpu_fence) {
+ TRACE_EVENT1("gpu", __func__, "frame", frame_index);
+ DVLOG(3) << __func__ << ": frame=" << frame_index;
+
+ ar_image_transport_->ServerWaitForGpuFence(std::move(gpu_fence));
+
+ DCHECK(webxr_->HaveProcessingFrame());
+ DCHECK(ar_image_transport_->UseSharedBuffer());
+ webxr_->GetProcessingFrame()->time_copied = base::TimeTicks::Now();
+ webxr_->TransitionFrameProcessingToRendering();
+
+ glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, 0);
+ ar_image_transport_->CopyDrawnImageToFramebuffer(
+ webxr_.get(), camera_image_size_, shared_buffer_transform_);
+
+ FinishFrame(frame_index);
+
+ webxr_->EndFrameRendering();
+
+ if (submit_client_) {
+ // Create a local GpuFence and pass it to the Renderer via IPC.
+ std::unique_ptr<gl::GLFence> gl_fence = gl::GLFence::CreateForGpuFence();
+ std::unique_ptr<gfx::GpuFence> gpu_fence2 = gl_fence->GetGpuFence();
+ submit_client_->OnSubmitFrameGpuFence(
+ gpu_fence2->GetGpuFenceHandle().Clone());
+ }
+ // We finished processing a frame, unblock a potentially waiting next frame.
+ webxr_->TryDeferredProcessing();
+}
+
+void ArCoreGl::UpdateLayerBounds(int16_t frame_index,
+ const gfx::RectF& left_bounds,
+ const gfx::RectF& right_bounds,
+ const gfx::Size& source_size) {
+ DVLOG(2) << __func__ << " source_size=" << source_size.ToString();
+
+ transfer_size_ = source_size;
+}
+
+void ArCoreGl::GetEnvironmentIntegrationProvider(
+ mojo::PendingAssociatedReceiver<
+ device::mojom::XREnvironmentIntegrationProvider> environment_provider) {
+ DVLOG(3) << __func__;
+
+ DCHECK(IsOnGlThread());
+ DCHECK(is_initialized_);
+
+ environment_receiver_.reset();
+ environment_receiver_.Bind(std::move(environment_provider));
+ environment_receiver_.set_disconnect_handler(base::BindOnce(
+ &ArCoreGl::OnBindingDisconnect, weak_ptr_factory_.GetWeakPtr()));
+}
+
+void ArCoreGl::SetInputSourceButtonListener(
+ mojo::PendingAssociatedRemote<device::mojom::XRInputSourceButtonListener>) {
+ // Input eventing is not supported. This call should not
+ // be made on this device.
+ mojo::ReportBadMessage("Input eventing is not supported.");
+}
+
+void ArCoreGl::SubscribeToHitTest(
+ mojom::XRNativeOriginInformationPtr native_origin_information,
+ const std::vector<mojom::EntityTypeForHitTest>& entity_types,
+ mojom::XRRayPtr ray,
+ mojom::XREnvironmentIntegrationProvider::SubscribeToHitTestCallback
+ callback) {
+ DVLOG(2) << __func__ << ": ray origin=" << ray->origin.ToString()
+ << ", ray direction=" << ray->direction.ToString();
+
+ // Input source state information is known to ArCoreGl and not to ArCore -
+ // check if we recognize the input source id.
+
+ if (native_origin_information->is_input_source_id()) {
+ DVLOG(1) << __func__
+ << ": ARCore device supports only transient input sources for "
+ "now. Rejecting subscription request.";
+ std::move(callback).Run(
+ device::mojom::SubscribeToHitTestResult::FAILURE_GENERIC, 0);
+ return;
+ }
+
+ base::Optional<uint64_t> maybe_subscription_id = arcore_->SubscribeToHitTest(
+ std::move(native_origin_information), entity_types, std::move(ray));
+
+ if (maybe_subscription_id) {
+ DVLOG(2) << __func__ << ": subscription_id=" << *maybe_subscription_id;
+ std::move(callback).Run(device::mojom::SubscribeToHitTestResult::SUCCESS,
+ *maybe_subscription_id);
+ } else {
+ DVLOG(1) << __func__ << ": subscription failed";
+ std::move(callback).Run(
+ device::mojom::SubscribeToHitTestResult::FAILURE_GENERIC, 0);
+ }
+}
+
+void ArCoreGl::SubscribeToHitTestForTransientInput(
+ const std::string& profile_name,
+ const std::vector<mojom::EntityTypeForHitTest>& entity_types,
+ mojom::XRRayPtr ray,
+ mojom::XREnvironmentIntegrationProvider::
+ SubscribeToHitTestForTransientInputCallback callback) {
+ DVLOG(2) << __func__ << ": ray origin=" << ray->origin.ToString()
+ << ", ray direction=" << ray->direction.ToString();
+
+ base::Optional<uint64_t> maybe_subscription_id =
+ arcore_->SubscribeToHitTestForTransientInput(profile_name, entity_types,
+ std::move(ray));
+
+ if (maybe_subscription_id) {
+ DVLOG(2) << __func__ << ": subscription_id=" << *maybe_subscription_id;
+ std::move(callback).Run(device::mojom::SubscribeToHitTestResult::SUCCESS,
+ *maybe_subscription_id);
+ } else {
+ DVLOG(1) << __func__ << ": subscription failed";
+ std::move(callback).Run(
+ device::mojom::SubscribeToHitTestResult::FAILURE_GENERIC, 0);
+ }
+}
+
+void ArCoreGl::UnsubscribeFromHitTest(uint64_t subscription_id) {
+ DVLOG(2) << __func__;
+
+ arcore_->UnsubscribeFromHitTest(subscription_id);
+}
+
+void ArCoreGl::CreateAnchor(
+ mojom::XRNativeOriginInformationPtr native_origin_information,
+ const device::Pose& native_origin_from_anchor,
+ CreateAnchorCallback callback) {
+ DVLOG(2) << __func__;
+
+ DCHECK(native_origin_information);
+
+ arcore_->CreateAnchor(*native_origin_information, native_origin_from_anchor,
+ std::move(callback));
+}
+
+void ArCoreGl::CreatePlaneAnchor(
+ mojom::XRNativeOriginInformationPtr native_origin_information,
+ const device::Pose& native_origin_from_anchor,
+ uint64_t plane_id,
+ CreatePlaneAnchorCallback callback) {
+ DVLOG(2) << __func__ << ": plane_id=" << plane_id;
+
+ DCHECK(native_origin_information);
+ DCHECK(plane_id);
+
+ arcore_->CreatePlaneAttachedAnchor(*native_origin_information,
+ native_origin_from_anchor, plane_id,
+ std::move(callback));
+}
+
+void ArCoreGl::DetachAnchor(uint64_t anchor_id) {
+ DVLOG(2) << __func__;
+
+ arcore_->DetachAnchor(anchor_id);
+}
+
+void ArCoreGl::SetFrameDataRestricted(bool frame_data_restricted) {
+ DCHECK(IsOnGlThread());
+ DCHECK(is_initialized_);
+
+ DVLOG(3) << __func__ << ": frame_data_restricted=" << frame_data_restricted;
+ restrict_frame_data_ = frame_data_restricted;
+ if (restrict_frame_data_) {
+ Pause();
+ } else {
+ Resume();
+ }
+}
+
+void ArCoreGl::ProcessFrame(
+ mojom::XRFrameDataRequestOptionsPtr options,
+ mojom::XRFrameDataPtr frame_data,
+ mojom::XRFrameDataProvider::GetFrameDataCallback callback) {
+ DVLOG(3) << __func__ << " frame=" << frame_data->frame_id << ", pose valid? "
+ << (frame_data->pose ? true : false);
+
+ DCHECK(IsOnGlThread());
+ DCHECK(is_initialized_);
+
+ if (frame_data->pose) {
+ DCHECK(frame_data->pose->position);
+ DCHECK(frame_data->pose->orientation);
+
+ frame_data->input_state = GetInputSourceStates();
+
+ device::Pose mojo_from_viewer(*frame_data->pose->position,
+ *frame_data->pose->orientation);
+
+ // Get results for hit test subscriptions.
+ frame_data->hit_test_subscription_results =
+ arcore_->GetHitTestSubscriptionResults(mojo_from_viewer.ToTransform(),
+ *frame_data->input_state);
+
+ arcore_->ProcessAnchorCreationRequests(
+ mojo_from_viewer.ToTransform(), *frame_data->input_state,
+ frame_data->time_delta + base::TimeTicks());
+ }
+
+ // Get anchors data, including anchors created this frame.
+ frame_data->anchors_data = arcore_->GetAnchorsData();
+
+ // Get planes data if it was requested.
+ if (IsFeatureEnabled(device::mojom::XRSessionFeature::PLANE_DETECTION)) {
+ frame_data->detected_planes_data = arcore_->GetDetectedPlanesData();
+ }
+
+ // Get lighting estimation data if it was requested.
+ if (options && options->include_lighting_estimation_data) {
+ frame_data->light_estimation_data = arcore_->GetLightEstimationData();
+ }
+
+ if (IsFeatureEnabled(device::mojom::XRSessionFeature::DEPTH)) {
+ frame_data->depth_data = arcore_->GetDepthData();
+ }
+
+ // Running this callback after resolving all the hit-test requests ensures
+ // that we satisfy the guarantee of the WebXR hit-test spec - that the
+ // hit-test promise resolves immediately prior to the frame for which it is
+ // valid.
+ std::move(callback).Run(std::move(frame_data));
+}
+
+void ArCoreGl::OnScreenTouch(bool is_primary,
+ bool touching,
+ int32_t pointer_id,
+ const gfx::PointF& touch_point) {
+ DVLOG(2) << __func__ << ": is_primary=" << is_primary
+ << ", pointer_id=" << pointer_id << ", touching=" << touching
+ << ", touch_point=" << touch_point.ToString();
+
+ if (!base::Contains(pointer_id_to_input_source_id_, pointer_id)) {
+ // assign ID
+ DCHECK(next_input_source_id_ != 0) << "ID equal to 0 cannot be used!";
+ pointer_id_to_input_source_id_[pointer_id] = next_input_source_id_;
+
+ DVLOG(3)
+ << __func__
+ << " : pointer id not previously recognized, assigned input source id="
+ << next_input_source_id_;
+
+ // Overflow is defined behavior for unsigned integers, just make sure that
+ // we never send out ID = 0.
+ next_input_source_id_++;
+ if (next_input_source_id_ == 0) {
+ next_input_source_id_ = 1;
+ }
+ }
+
+ uint32_t inputSourceId = pointer_id_to_input_source_id_[pointer_id];
+ ScreenTouchEvent& screen_touch_event = screen_touch_events_[inputSourceId];
+
+ screen_touch_event.pointer_id = pointer_id;
+ screen_touch_event.is_primary = is_primary;
+ screen_touch_event.screen_last_touch = touch_point;
+ screen_touch_event.screen_touch_active = touching;
+ if (touching) {
+ screen_touch_event.screen_touch_pending = true;
+ }
+}
+
+std::vector<mojom::XRInputSourceStatePtr> ArCoreGl::GetInputSourceStates() {
+ DVLOG(3) << __func__;
+
+ std::vector<mojom::XRInputSourceStatePtr> result;
+
+ for (auto& id_and_touch_event : screen_touch_events_) {
+ bool is_primary = id_and_touch_event.second.is_primary;
+ bool screen_touch_pending = id_and_touch_event.second.screen_touch_pending;
+ bool screen_touch_active = id_and_touch_event.second.screen_touch_active;
+ gfx::PointF screen_last_touch = id_and_touch_event.second.screen_last_touch;
+
+ DVLOG(3) << __func__
+ << " : pointer for input source id=" << id_and_touch_event.first
+ << ", pointer_id=" << id_and_touch_event.second.pointer_id
+ << ", active=" << screen_touch_active
+ << ", pending=" << screen_touch_pending;
+
+ // If there's no active screen touch, and no unreported past click
+ // event, don't report a device.
+ if (!screen_touch_pending && !screen_touch_active) {
+ continue;
+ }
+
+ device::mojom::XRInputSourceStatePtr state =
+ device::mojom::XRInputSourceState::New();
+
+ state->source_id = id_and_touch_event.first;
+
+ state->is_auxiliary = !is_primary;
+
+ state->primary_input_pressed = screen_touch_active;
+
+ // If the touch is not active but pending, it means that it was clicked
+ // within a single frame.
+ if (!screen_touch_active && screen_touch_pending) {
+ state->primary_input_clicked = true;
+
+ // Clear screen_touch_pending for this input source - we have consumed it.
+ id_and_touch_event.second.screen_touch_pending = false;
+ }
+
+ // Save the touch point for use in Blink's XR input event deduplication.
+ state->overlay_pointer_position = screen_last_touch;
+
+ state->description = device::mojom::XRInputSourceDescription::New();
+
+ state->description->handedness = device::mojom::XRHandedness::NONE;
+
+ state->description->target_ray_mode =
+ device::mojom::XRTargetRayMode::TAPPING;
+
+ state->description->profiles.push_back(kInputSourceProfileName);
+
+ // Controller doesn't have a measured position.
+ state->emulated_position = true;
+
+ // The Renderer code ignores state->grip for TAPPING (screen-based) target
+ // ray mode, so we don't bother filling it in here. If this does get used at
+ // some point in the future, this should be set to the inverse of the
+ // pose rigid transform.
+
+ // Get a viewer-space ray from screen-space coordinates by applying the
+ // inverse of the projection matrix. Z coordinate of -1 means the point will
+ // be projected onto the projection matrix near plane. See also
+ // third_party/blink/renderer/modules/xr/xr_view.cc's UnprojectPointer.
+ const float x_normalized =
+ screen_last_touch.x() / camera_image_size_.width() * 2.f - 1.f;
+ const float y_normalized =
+ (1.f - screen_last_touch.y() / camera_image_size_.height()) * 2.f - 1.f;
+ gfx::Point3F touch_point(x_normalized, y_normalized, -1.f);
+ DVLOG(3) << __func__ << ": touch_point=" << touch_point.ToString();
+ inverse_projection_.TransformPoint(&touch_point);
+ DVLOG(3) << __func__ << ": unprojected=" << touch_point.ToString();
+
+ // Ray points along -Z in ray space, so we need to flip it to get
+ // the +Z axis unit vector.
+ gfx::Vector3dF ray_backwards(-touch_point.x(), -touch_point.y(),
+ -touch_point.z());
+ gfx::Vector3dF new_z;
+ bool can_normalize = ray_backwards.GetNormalized(&new_z);
+ DCHECK(can_normalize);
+
+ // Complete the ray-space basis by adding X and Y unit
+ // vectors based on cross products.
+ const gfx::Vector3dF kUp(0.f, 1.f, 0.f);
+ gfx::Vector3dF new_x(kUp);
+ new_x.Cross(new_z);
+ new_x.GetNormalized(&new_x);
+ gfx::Vector3dF new_y(new_z);
+ new_y.Cross(new_x);
+ new_y.GetNormalized(&new_y);
+
+ // Fill in the transform matrix in row-major order. The first three columns
+ // contain the basis vectors, the fourth column the position offset.
+ gfx::Transform viewer_from_pointer(
+ new_x.x(), new_y.x(), new_z.x(), touch_point.x(), // row 1
+ new_x.y(), new_y.y(), new_z.y(), touch_point.y(), // row 2
+ new_x.z(), new_y.z(), new_z.z(), touch_point.z(), // row 3
+ 0, 0, 0, 1);
+ DVLOG(3) << __func__ << ": viewer_from_pointer=\n"
+ << viewer_from_pointer.ToString();
+
+ state->description->input_from_pointer = viewer_from_pointer;
+
+ // Create the gamepad object and modify necessary fields.
+ state->gamepad = device::Gamepad{};
+ state->gamepad->connected = true;
+ state->gamepad->id[0] = '\0';
+ state->gamepad->timestamp =
+ base::TimeTicks::Now().since_origin().InMicroseconds();
+
+ state->gamepad->axes_length = 2;
+ state->gamepad->axes[0] = x_normalized;
+ state->gamepad->axes[1] =
+ -y_normalized; // Gamepad's Y axis is actually
+ // inverted (1.0 means "backward").
+
+ state->gamepad->buttons_length = 3; // 2 placeholders + the real one
+ // Default-constructed buttons are already valid placeholders.
+ state->gamepad->buttons[2].touched = true;
+ state->gamepad->buttons[2].value = 1.0;
+ state->gamepad->mapping = device::GamepadMapping::kNone;
+ state->gamepad->hand = device::GamepadHand::kNone;
+
+ result.push_back(std::move(state));
+ }
+
+ // All the input source IDs that are no longer touching need to remain unused
+ // for at least one frame. For now, we always assign new ID for input source
+ // so there's no need to remember the IDs that have to be put on hold. Just
+ // clean up all the no longer touching pointers:
+ std::unordered_map<uint32_t, ScreenTouchEvent> still_touching_events;
+ for (const auto& screen_touch_event : screen_touch_events_) {
+ if (!screen_touch_event.second.screen_touch_active) {
+ // This pointer is no longer touching - remove it from the mapping, do not
+ // consider it as still touching:
+ pointer_id_to_input_source_id_.erase(
+ screen_touch_event.second.pointer_id);
+ } else {
+ still_touching_events.insert(screen_touch_event);
+ }
+ }
+
+ screen_touch_events_.swap(still_touching_events);
+
+ return result;
+}
+
+bool ArCoreGl::IsFeatureEnabled(mojom::XRSessionFeature feature) {
+ return base::Contains(enabled_features_, feature);
+}
+
+void ArCoreGl::Pause() {
+ DCHECK(IsOnGlThread());
+ DCHECK(is_initialized_);
+ DVLOG(1) << __func__;
+
+ arcore_->Pause();
+ is_paused_ = true;
+}
+
+void ArCoreGl::Resume() {
+ DCHECK(IsOnGlThread());
+ DCHECK(is_initialized_);
+ DVLOG(1) << __func__;
+
+ arcore_->Resume();
+ is_paused_ = false;
+}
+
+void ArCoreGl::OnBindingDisconnect() {
+ DVLOG(3) << __func__;
+
+ CloseBindingsIfOpen();
+
+ std::move(session_shutdown_callback_).Run();
+}
+
+void ArCoreGl::CloseBindingsIfOpen() {
+ DVLOG(3) << __func__;
+
+ environment_receiver_.reset();
+ frame_data_receiver_.reset();
+ session_controller_receiver_.reset();
+ presentation_receiver_.reset();
+}
+
+bool ArCoreGl::IsOnGlThread() const {
+ return gl_thread_task_runner_->BelongsToCurrentThread();
+}
+
+base::WeakPtr<ArCoreGl> ArCoreGl::GetWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+}
+
+} // namespace device
diff --git a/chromium/device/vr/android/arcore/arcore_gl.h b/chromium/device/vr/android/arcore/arcore_gl.h
new file mode 100644
index 00000000000..8d790cc3f8c
--- /dev/null
+++ b/chromium/device/vr/android/arcore/arcore_gl.h
@@ -0,0 +1,327 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_VR_ANDROID_ARCORE_ARCORE_GL_H_
+#define DEVICE_VR_ANDROID_ARCORE_ARCORE_GL_H_
+
+#include <memory>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "base/cancelable_callback.h"
+#include "base/containers/queue.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/single_thread_task_runner.h"
+#include "device/vr/public/mojom/isolated_xr_service.mojom.h"
+#include "device/vr/public/mojom/vr_service.mojom.h"
+#include "device/vr/util/fps_meter.h"
+#include "mojo/public/cpp/bindings/associated_receiver.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
+#include "mojo/public/cpp/bindings/pending_associated_remote.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "ui/display/display.h"
+#include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/geometry/quaternion.h"
+#include "ui/gfx/geometry/rect_f.h"
+#include "ui/gfx/geometry/size_f.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace gfx {
+class GpuFence;
+} // namespace gfx
+
+namespace gl {
+class GLContext;
+class GLSurface;
+} // namespace gl
+
+namespace vr {
+class ArCoreSessionUtils;
+class WebXrPresentationState;
+} // namespace vr
+
+namespace device {
+
+class ArCore;
+class ArCoreFactory;
+class ArImageTransport;
+
+using ArCoreGlCreateSessionCallback = base::OnceCallback<void(
+ mojo::PendingRemote<mojom::XRFrameDataProvider> frame_data_provider,
+ mojom::VRDisplayInfoPtr display_info,
+ mojo::PendingRemote<mojom::XRSessionController> session_controller,
+ mojom::XRPresentationConnectionPtr presentation_connection)>;
+
+// All of this class's methods must be called on the same valid GL thread with
+// the exception of GetGlThreadTaskRunner() and GetWeakPtr().
+class ArCoreGl : public mojom::XRFrameDataProvider,
+ public mojom::XRPresentationProvider,
+ public mojom::XREnvironmentIntegrationProvider,
+ public mojom::XRSessionController {
+ public:
+ explicit ArCoreGl(std::unique_ptr<ArImageTransport> ar_image_transport);
+ ~ArCoreGl() override;
+
+ void Initialize(
+ vr::ArCoreSessionUtils* session_utils,
+ ArCoreFactory* arcore_factory,
+ gfx::AcceleratedWidget drawing_widget,
+ const gfx::Size& frame_size,
+ display::Display::Rotation display_rotation,
+ const std::vector<device::mojom::XRSessionFeature>& enabled_features,
+ base::OnceCallback<void(bool)> callback);
+
+ void CreateSession(mojom::VRDisplayInfoPtr display_info,
+ ArCoreGlCreateSessionCallback create_callback,
+ base::OnceClosure shutdown_callback);
+
+ const scoped_refptr<base::SingleThreadTaskRunner>& GetGlThreadTaskRunner() {
+ return gl_thread_task_runner_;
+ }
+
+ // mojom::XRFrameDataProvider
+ void GetFrameData(mojom::XRFrameDataRequestOptionsPtr options,
+ GetFrameDataCallback callback) override;
+
+ void GetEnvironmentIntegrationProvider(
+ mojo::PendingAssociatedReceiver<mojom::XREnvironmentIntegrationProvider>
+ environment_provider) override;
+ void SetInputSourceButtonListener(
+ mojo::PendingAssociatedRemote<device::mojom::XRInputSourceButtonListener>)
+ override;
+
+ // XRPresentationProvider
+ void SubmitFrameMissing(int16_t frame_index, const gpu::SyncToken&) override;
+ void SubmitFrame(int16_t frame_index,
+ const gpu::MailboxHolder& mailbox,
+ base::TimeDelta time_waited) override;
+ void SubmitFrameWithTextureHandle(
+ int16_t frame_index,
+ mojo::PlatformHandle texture_handle) override;
+ void SubmitFrameDrawnIntoTexture(int16_t frame_index,
+ const gpu::SyncToken&,
+ base::TimeDelta time_waited) override;
+ void UpdateLayerBounds(int16_t frame_index,
+ const gfx::RectF& left_bounds,
+ const gfx::RectF& right_bounds,
+ const gfx::Size& source_size) override;
+
+ // XREnvironmentIntegrationProvider
+ void SubscribeToHitTest(
+ mojom::XRNativeOriginInformationPtr native_origin_information,
+ const std::vector<mojom::EntityTypeForHitTest>& entity_types,
+ mojom::XRRayPtr ray,
+ mojom::XREnvironmentIntegrationProvider::SubscribeToHitTestCallback
+ callback) override;
+ void SubscribeToHitTestForTransientInput(
+ const std::string& profile_name,
+ const std::vector<mojom::EntityTypeForHitTest>& entity_types,
+ mojom::XRRayPtr ray,
+ mojom::XREnvironmentIntegrationProvider::
+ SubscribeToHitTestForTransientInputCallback callback) override;
+
+ void UnsubscribeFromHitTest(uint64_t subscription_id) override;
+
+ void CreateAnchor(
+ mojom::XRNativeOriginInformationPtr native_origin_information,
+ const device::Pose& native_origin_from_anchor,
+ CreateAnchorCallback callback) override;
+ void CreatePlaneAnchor(
+ mojom::XRNativeOriginInformationPtr native_origin_information,
+ const device::Pose& native_origin_from_anchor,
+ uint64_t plane_id,
+ CreatePlaneAnchorCallback callback) override;
+
+ void DetachAnchor(uint64_t anchor_id) override;
+
+ // mojom::XRSessionController
+ void SetFrameDataRestricted(bool restricted) override;
+
+ void ProcessFrameFromMailbox(int16_t frame_index,
+ const gpu::MailboxHolder& mailbox);
+ void ProcessFrameDrawnIntoTexture(int16_t frame_index,
+ const gpu::SyncToken& sync_token);
+ void OnWebXrTokenSignaled(int16_t frame_index,
+ std::unique_ptr<gfx::GpuFence> gpu_fence);
+
+ // Notifies that the screen was touched at |touch_point| using a pointer.
+ // |touching| will be set to true if the screen is still touched. |is_primary|
+ // signifies that the used pointer is considered primary.
+ void OnScreenTouch(bool is_primary,
+ bool touching,
+ int32_t pointer_id,
+ const gfx::PointF& touch_point);
+ std::vector<mojom::XRInputSourceStatePtr> GetInputSourceStates();
+
+ base::WeakPtr<ArCoreGl> GetWeakPtr();
+
+ private:
+ void Pause();
+ void Resume();
+
+ void FinishFrame(int16_t frame_index);
+ bool IsSubmitFrameExpected(int16_t frame_index);
+ void ProcessFrame(mojom::XRFrameDataRequestOptionsPtr options,
+ mojom::XRFrameDataPtr frame_data,
+ mojom::XRFrameDataProvider::GetFrameDataCallback callback);
+
+ bool InitializeGl(gfx::AcceleratedWidget drawing_widget);
+ void OnArImageTransportReady(base::OnceCallback<void(bool)> callback);
+ bool IsOnGlThread() const;
+ void CopyCameraImageToFramebuffer();
+ void OnTransportFrameAvailable(const gfx::Transform& uv_transform);
+
+ // Use a helper method to avoid storing the mojo getframedata callback
+ // in a closure owned by the task runner, that would lead to inconsistent
+ // state on session shutdown. See https://crbug.com/1065572.
+ void RunNextGetFrameData();
+
+ bool IsFeatureEnabled(mojom::XRSessionFeature feature);
+
+ // Set of features enabled on this session. Required to correctly configure
+ // the session and only send out necessary data related to reference spaces to
+ // blink. Valid after the call to |Initialize()| method.
+ std::unordered_set<device::mojom::XRSessionFeature> enabled_features_;
+
+ base::OnceClosure session_shutdown_callback_;
+
+ scoped_refptr<gl::GLSurface> surface_;
+ scoped_refptr<gl::GLContext> context_;
+ scoped_refptr<base::SingleThreadTaskRunner> gl_thread_task_runner_;
+
+ // Created on GL thread and should only be accessed on that thread.
+ std::unique_ptr<ArCore> arcore_;
+ std::unique_ptr<ArImageTransport> ar_image_transport_;
+
+ // This class uses the same overall presentation state logic
+ // as GvrGraphicsDelegate, with some difference due to drawing
+ // camera images even on frames with no pose and therefore
+ // no blink-generated rendered image.
+ //
+ // Rough sequence is:
+ //
+ // SubmitFrame N N animating->processing
+ // draw camera N
+ // waitForToken
+ // GetFrameData N+1 N+1 start animating
+ // update ARCore N to N+1
+ // OnToken N N processing->rendering
+ // draw rendered N
+ // swap N rendering done
+ // SubmitFrame N+1 N+1 animating->processing
+ // draw camera N+1
+ // waitForToken
+ std::unique_ptr<vr::WebXrPresentationState> webxr_;
+
+ // Default dummy values to ensure consistent behaviour.
+
+ // Transfer size is the size of the WebGL framebuffer, this may be
+ // smaller than the camera image if framebufferScaleFactor is < 1.0.
+ gfx::Size transfer_size_ = gfx::Size(0, 0);
+
+ // The camera image size stays locked to the screen size even if
+ // framebufferScaleFactor changes.
+ gfx::Size camera_image_size_ = gfx::Size(0, 0);
+ display::Display::Rotation display_rotation_ = display::Display::ROTATE_0;
+ bool should_update_display_geometry_ = true;
+
+ // UV transform for drawing the camera texture, this is supplied by ARCore
+ // and can include 90 degree rotations or other nontrivial transforms.
+ gfx::Transform uv_transform_;
+
+ // UV transform for drawing received WebGL content from a shared buffer's
+ // texture, this is simply an identity.
+ gfx::Transform shared_buffer_transform_;
+
+ gfx::Transform projection_;
+ gfx::Transform inverse_projection_;
+ // The first run of ProduceFrame should set uv_transform_ and projection_
+ // using the default settings in ArCore.
+ bool should_recalculate_uvs_ = true;
+ bool have_camera_image_ = false;
+
+ bool is_initialized_ = false;
+ bool is_paused_ = true;
+
+ bool restrict_frame_data_ = false;
+
+ base::TimeTicks arcore_update_next_expected_;
+ base::TimeDelta arcore_last_frame_timestamp_;
+ base::TimeDelta arcore_frame_interval_;
+ FPSMeter fps_meter_;
+
+ mojo::Receiver<mojom::XRFrameDataProvider> frame_data_receiver_{this};
+ mojo::Receiver<mojom::XRSessionController> session_controller_receiver_{this};
+ mojo::AssociatedReceiver<mojom::XREnvironmentIntegrationProvider>
+ environment_receiver_{this};
+
+ void OnBindingDisconnect();
+ void CloseBindingsIfOpen();
+
+ mojo::Receiver<device::mojom::XRPresentationProvider> presentation_receiver_{
+ this};
+ mojo::Remote<device::mojom::XRPresentationClient> submit_client_;
+
+ // This closure saves arguments for the next GetFrameData call, including a
+ // mojo callback. Must remain owned by ArCoreGl, don't pass it off to the task
+ // runner directly. See RunNextGetFrameData() comments.
+ base::OnceClosure pending_getframedata_;
+
+ mojom::VRDisplayInfoPtr display_info_;
+ bool display_info_changed_ = false;
+
+ // Currently estimated floor height.
+ base::Optional<float> floor_height_estimate_;
+
+ // Touch-related data.
+ // Android will report touch events via MotionEvent - see ArImmersiveOverlay
+ // for details.
+ struct ScreenTouchEvent {
+ gfx::PointF screen_last_touch;
+
+ // Screen touch start/end events get reported asynchronously. We want to
+ // report at least one "clicked" event even if start and end happen within a
+ // single frame. The "active" state corresponds to the current state and is
+ // updated asynchronously. The "pending" state is set to true whenever the
+ // screen is touched, but only gets cleared by the input source handler.
+ //
+ // active pending event
+ // 0 0
+ // 1 1
+ // 1 1 pressed=true (selectstart)
+ // 1 1 pressed=true
+ // 0 1->0 pressed=false clicked=true (selectend, click)
+ //
+ // 0 0
+ // 1 1
+ // 0 1
+ // 0 1->0 pressed=false clicked=true (selectend, click)
+ float screen_touch_pending = false;
+ float screen_touch_active = false;
+
+ // ID of the pointer that raised this event.
+ int32_t pointer_id;
+ bool is_primary;
+ };
+
+ // Map from input source ID to its latest information.
+ std::unordered_map<uint32_t, ScreenTouchEvent> screen_touch_events_;
+ // Map from pointer ID to input source ID currently assigned to that pointer.
+ std::unordered_map<int32_t, uint32_t> pointer_id_to_input_source_id_;
+
+ uint32_t next_input_source_id_ = 1;
+
+ // Must be last.
+ base::WeakPtrFactory<ArCoreGl> weak_ptr_factory_{this};
+ DISALLOW_COPY_AND_ASSIGN(ArCoreGl);
+};
+
+} // namespace device
+
+#endif // DEVICE_VR_ANDROID_ARCORE_ARCORE_GL_H_
diff --git a/chromium/device/vr/android/arcore/arcore_gl_thread.cc b/chromium/device/vr/android/arcore/arcore_gl_thread.cc
new file mode 100644
index 00000000000..cd04fcb65de
--- /dev/null
+++ b/chromium/device/vr/android/arcore/arcore_gl_thread.cc
@@ -0,0 +1,46 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/vr/android/arcore/arcore_gl_thread.h"
+
+#include <utility>
+#include "base/version.h"
+#include "device/vr/android/arcore/ar_image_transport.h"
+#include "device/vr/android/arcore/arcore_gl.h"
+
+namespace device {
+
+ArCoreGlThread::ArCoreGlThread(
+ std::unique_ptr<ArImageTransportFactory> ar_image_transport_factory,
+ std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge,
+ base::OnceCallback<void()> initialized_callback)
+ : base::android::JavaHandlerThread("ArCoreGL"),
+ ar_image_transport_factory_(std::move(ar_image_transport_factory)),
+ mailbox_bridge_(std::move(mailbox_bridge)),
+ initialized_callback_(std::move(initialized_callback)) {
+ DVLOG(3) << __func__;
+}
+
+ArCoreGlThread::~ArCoreGlThread() {
+ DVLOG(3) << __func__;
+ Stop();
+}
+
+ArCoreGl* ArCoreGlThread::GetArCoreGl() {
+ return arcore_gl_.get();
+}
+
+void ArCoreGlThread::Init() {
+ DCHECK(!arcore_gl_);
+
+ arcore_gl_ = std::make_unique<ArCoreGl>(
+ ar_image_transport_factory_->Create(std::move(mailbox_bridge_)));
+ std::move(initialized_callback_).Run();
+}
+
+void ArCoreGlThread::CleanUp() {
+ arcore_gl_.reset();
+}
+
+} // namespace device
diff --git a/chromium/device/vr/android/arcore/arcore_gl_thread.h b/chromium/device/vr/android/arcore/arcore_gl_thread.h
new file mode 100644
index 00000000000..a8a857836cf
--- /dev/null
+++ b/chromium/device/vr/android/arcore/arcore_gl_thread.h
@@ -0,0 +1,47 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_VR_ANDROID_ARCORE_ARCORE_GL_THREAD_H_
+#define DEVICE_VR_ANDROID_ARCORE_ARCORE_GL_THREAD_H_
+
+#include <memory>
+#include "base/android/java_handler_thread.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/single_thread_task_runner.h"
+#include "device/vr/android/mailbox_to_surface_bridge.h"
+
+namespace device {
+
+class ArCoreGl;
+class ArImageTransportFactory;
+class MailboxToSurfaceBridge;
+
+class ArCoreGlThread : public base::android::JavaHandlerThread {
+ public:
+ ArCoreGlThread(
+ std::unique_ptr<ArImageTransportFactory> ar_image_transport_factory,
+ std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge,
+ base::OnceCallback<void()> initialized_callback);
+ ~ArCoreGlThread() override;
+ ArCoreGl* GetArCoreGl();
+
+ protected:
+ void Init() override;
+ void CleanUp() override;
+
+ private:
+ std::unique_ptr<ArImageTransportFactory> ar_image_transport_factory_;
+ std::unique_ptr<MailboxToSurfaceBridge> mailbox_bridge_;
+ base::OnceCallback<void()> initialized_callback_;
+
+ // Created on GL thread.
+ std::unique_ptr<ArCoreGl> arcore_gl_;
+
+ DISALLOW_COPY_AND_ASSIGN(ArCoreGlThread);
+};
+
+} // namespace device
+
+#endif // DEVICE_VR_ANDROID_ARCORE_ARCORE_GL_THREAD_H_
diff --git a/chromium/device/vr/android/arcore/arcore_impl.cc b/chromium/device/vr/android/arcore/arcore_impl.cc
index 88262a2c3ef..7b1d8fb1c6f 100644
--- a/chromium/device/vr/android/arcore/arcore_impl.cc
+++ b/chromium/device/vr/android/arcore/arcore_impl.cc
@@ -6,10 +6,13 @@
#include "base/android/jni_android.h"
#include "base/bind.h"
+#include "base/containers/span.h"
+#include "base/numerics/checked_math.h"
#include "base/numerics/math_constants.h"
#include "base/optional.h"
#include "base/trace_event/trace_event.h"
#include "base/util/type_safety/pass_key.h"
+#include "device/vr/android/arcore/arcore_math_utils.h"
#include "device/vr/android/arcore/arcore_plane_manager.h"
#include "device/vr/android/arcore/type_converters.h"
#include "device/vr/public/mojom/pose.h"
@@ -92,50 +95,59 @@ void ReleaseArCoreCubemap(ArImageCubemap* cube_map) {
memset(cube_map, 0, sizeof(*cube_map));
}
-void CopyArCoreImage_RGBA16F(const ArSession* session,
- const ArImage* image,
- int32_t plane_index,
- std::vector<device::RgbaTupleF16>* out_pixels,
- uint32_t* out_width,
- uint32_t* out_height) {
- // Get source image information
- int32_t width = 0, height = 0, src_row_stride = 0, src_pixel_stride = 0;
- ArImage_getWidth(session, image, &width);
- ArImage_getHeight(session, image, &height);
+// Helper, copies ARCore image to the passed in buffer, assuming that the caller
+// allocated the buffer to fit all the data.
+template <typename T>
+void CopyArCoreImage(const ArSession* session,
+ const ArImage* image,
+ int32_t plane_index,
+ base::span<T> out_pixels,
+ uint32_t width,
+ uint32_t height) {
+ DVLOG(3) << __func__ << ": width=" << width << ", height=" << height
+ << ", out_pixels.size()=" << out_pixels.size();
+
+ DCHECK_GE(out_pixels.size(), width * height);
+
+ int32_t src_row_stride = 0, src_pixel_stride = 0;
ArImage_getPlaneRowStride(session, image, plane_index, &src_row_stride);
ArImage_getPlanePixelStride(session, image, plane_index, &src_pixel_stride);
+ // Naked pointer since ArImage_getPlaneData does not transfer ownership to us.
uint8_t const* src_buffer = nullptr;
int32_t src_buffer_length = 0;
ArImage_getPlaneData(session, image, plane_index, &src_buffer,
&src_buffer_length);
- // Create destination
- *out_width = width;
- *out_height = height;
- out_pixels->resize(width * height);
-
// Fast path: Source and destination have the same layout
- bool const fast_path = static_cast<size_t>(src_row_stride) ==
- width * sizeof(device::RgbaTupleF16);
- TRACE_EVENT1("xr", "CopyArCoreImage_RGBA16F: memcpy", "fastPath", fast_path);
+ bool const fast_path =
+ static_cast<size_t>(src_row_stride) == width * sizeof(T);
+ TRACE_EVENT1("xr", "CopyArCoreImage: memcpy", "fastPath", fast_path);
+
+ DVLOG(3) << __func__ << ": plane_index=" << plane_index
+ << ", src_buffer_length=" << src_buffer_length
+ << ", src_row_stride=" << src_row_stride
+ << ", src_pixel_stride=" << src_pixel_stride
+ << ", fast_path=" << fast_path << ", sizeof(T)=" << sizeof(T);
// If they have the same layout, we can copy the entire buffer at once
if (fast_path) {
- DCHECK_EQ(out_pixels->size() * sizeof(device::RgbaTupleF16),
- static_cast<size_t>(src_buffer_length));
- memcpy(out_pixels->data(), src_buffer, src_buffer_length);
+ CHECK_EQ(out_pixels.size() * sizeof(T),
+ static_cast<size_t>(src_buffer_length));
+ memcpy(out_pixels.data(), src_buffer, src_buffer_length);
return;
}
+ CHECK_EQ(sizeof(T), static_cast<size_t>(src_pixel_stride));
+
// Slow path: copy pixel by pixel, row by row
- for (int32_t row = 0; row < height; ++row) {
+ for (uint32_t row = 0; row < height; ++row) {
auto* src = src_buffer + src_row_stride * row;
- auto* dest = out_pixels->data() + width * row;
+ auto* dest = out_pixels.data() + width * row;
// For each pixel
- for (int32_t x = 0; x < width; ++x) {
- memcpy(dest, src, sizeof(device::RgbaTupleF16));
+ for (uint32_t x = 0; x < width; ++x) {
+ memcpy(dest, src, sizeof(T));
src += src_pixel_stride;
dest += 1;
@@ -143,6 +155,30 @@ void CopyArCoreImage_RGBA16F(const ArSession* session,
}
}
+// Helper, copies ARCore image to the passed in vector, discovering the buffer
+// size and resizing the vector first.
+template <typename T>
+void CopyArCoreImage(const ArSession* session,
+ const ArImage* image,
+ int32_t plane_index,
+ std::vector<T>* out_pixels,
+ uint32_t* out_width,
+ uint32_t* out_height) {
+ // Get source image information
+ int32_t width = 0, height = 0;
+ ArImage_getWidth(session, image, &width);
+ ArImage_getHeight(session, image, &height);
+
+ *out_width = width;
+ *out_height = height;
+
+ // Allocate memory for the output.
+ out_pixels->resize(width * height);
+
+ CopyArCoreImage(session, image, plane_index, base::span<T>(*out_pixels),
+ width, height);
+}
+
device::mojom::XRLightProbePtr GetLightProbe(
ArSession* arcore_session,
ArLightEstimate* arcore_light_estimate) {
@@ -230,8 +266,8 @@ device::mojom::XRReflectionProbePtr GetReflectionProbe(
// Copy the cubemap
uint32_t face_width = 0, face_height = 0;
- CopyArCoreImage_RGBA16F(arcore_session, arcore_cube_map_face, 0,
- cube_map_face, &face_width, &face_height);
+ CopyArCoreImage(arcore_session, arcore_cube_map_face, 0, cube_map_face,
+ &face_width, &face_height);
// Make sure the cube map is square
if (face_width != face_height) {
@@ -303,7 +339,9 @@ ArCoreImpl::~ArCoreImpl() {
}
bool ArCoreImpl::Initialize(
- base::android::ScopedJavaLocalRef<jobject> context) {
+ base::android::ScopedJavaLocalRef<jobject> context,
+ const std::unordered_set<device::mojom::XRSessionFeature>&
+ enabled_features) {
DCHECK(IsOnGlThread());
DCHECK(!arcore_session_.is_valid());
@@ -346,6 +384,12 @@ bool ArCoreImpl::Initialize(
ArConfig_setLightEstimationMode(session.get(), arcore_config.get(),
AR_LIGHT_ESTIMATION_MODE_ENVIRONMENTAL_HDR);
+ if (base::Contains(enabled_features,
+ device::mojom::XRSessionFeature::DEPTH)) {
+ ArConfig_setDepthMode(session.get(), arcore_config.get(),
+ AR_DEPTH_MODE_AUTOMATIC);
+ }
+
status = ArSession_configure(session.get(), arcore_config.get());
if (status != AR_SUCCESS) {
DLOG(ERROR) << "ArSession_configure failed: " << status;
@@ -604,19 +648,25 @@ void ArCoreImpl::SetDisplayGeometry(
}
std::vector<float> ArCoreImpl::TransformDisplayUvCoords(
- const base::span<const float> uvs) {
+ const base::span<const float> uvs) const {
DCHECK(IsOnGlThread());
DCHECK(arcore_session_.is_valid());
DCHECK(arcore_frame_.is_valid());
size_t num_elements = uvs.size();
DCHECK(num_elements % 2 == 0);
- std::vector<float> uvs_out(num_elements);
+ DCHECK_GE(num_elements, 6u);
+ std::vector<float> uvs_out(num_elements);
ArFrame_transformCoordinates2d(
arcore_session_.get(), arcore_frame_.get(),
AR_COORDINATES_2D_VIEW_NORMALIZED, num_elements / 2, &uvs[0],
AR_COORDINATES_2D_TEXTURE_NORMALIZED, &uvs_out[0]);
+
+ DVLOG(3) << __func__ << ": transformed uvs=[ " << uvs_out[0] << " , "
+ << uvs_out[1] << " , " << uvs_out[2] << " , " << uvs_out[3] << " , "
+ << uvs_out[4] << " , " << uvs_out[5] << " ]";
+
return uvs_out;
}
@@ -1412,7 +1462,96 @@ void ArCoreImpl::DetachAnchor(uint64_t anchor_id) {
anchor_manager_->DetachAnchor(AnchorId(anchor_id));
}
-bool ArCoreImpl::IsOnGlThread() {
+mojom::XRDepthDataPtr ArCoreImpl::GetDepthData() {
+ DVLOG(3) << __func__;
+
+ internal::ScopedArCoreObject<ArImage*> ar_image;
+ ArStatus status = ArFrame_acquireDepthImage(
+ arcore_session_.get(), arcore_frame_.get(),
+ internal::ScopedArCoreObject<ArImage*>::Receiver(ar_image).get());
+
+ if (status != AR_SUCCESS) {
+ DVLOG(2) << __func__
+ << ": ArFrame_acquireDepthImage failed, status=" << status;
+ return nullptr;
+ }
+
+ int64_t timestamp_ns;
+ ArImage_getTimestamp(arcore_session_.get(), ar_image.get(), &timestamp_ns);
+ base::TimeDelta time_delta = base::TimeDelta::FromNanoseconds(timestamp_ns);
+ DVLOG(3) << __func__ << ": depth image time_delta=" << time_delta;
+
+ // The image returned from ArFrame_acquireDepthImage() is documented to have
+ // a single 16-bit plane at index 0. The ArImage format is documented to be
+ // AR_IMAGE_FORMAT_DEPTH16 (equivalent to ImageFormat.DEPTH16). There should
+ // be no need to validate this in non-debug builds.
+ // https://developers.google.com/ar/reference/c/group/ar-frame#arframe_acquiredepthimage
+ // https://developer.android.com/reference/android/graphics/ImageFormat#DEPTH16
+
+ ArImageFormat image_format;
+ ArImage_getFormat(arcore_session_.get(), ar_image.get(), &image_format);
+
+ CHECK_EQ(image_format, AR_IMAGE_FORMAT_DEPTH16)
+ << "Depth image format must be AR_IMAGE_FORMAT_DEPTH16, found: "
+ << image_format;
+
+ int32_t num_planes;
+ ArImage_getNumberOfPlanes(arcore_session_.get(), ar_image.get(), &num_planes);
+
+ CHECK_EQ(num_planes, 1) << "Depth image must have 1 plane, found: "
+ << num_planes;
+
+ if (time_delta > previous_depth_data_time_) {
+ mojom::XRDepthDataUpdatedPtr result = mojom::XRDepthDataUpdated::New();
+
+ result->time_delta = time_delta;
+
+ int32_t width = 0, height = 0;
+ ArImage_getWidth(arcore_session_.get(), ar_image.get(), &width);
+ ArImage_getHeight(arcore_session_.get(), ar_image.get(), &height);
+
+ DVLOG(3) << __func__ << ": depth image dimensions=" << width << "x"
+ << height;
+
+ // Depth image is defined as a width by height array of 2-byte elements:
+ auto checked_buffer_size = base::CheckMul<size_t>(2, width, height);
+
+ size_t buffer_size;
+ if (!checked_buffer_size.AssignIfValid(&buffer_size)) {
+ DVLOG(2) << __func__
+ << ": overflow in 2 * width * height expression, returning null "
+ "depth data";
+ return nullptr;
+ }
+
+ mojo_base::BigBuffer pixels(buffer_size);
+
+ // Interpret BigBuffer's data as a width by height array of uint16_t's and
+ // copy image data into it:
+ CopyArCoreImage(
+ arcore_session_.get(), ar_image.get(), 0,
+ base::span<uint16_t>(reinterpret_cast<uint16_t*>(pixels.data()),
+ pixels.size() / 2),
+ width, height);
+
+ result->pixel_data = std::move(pixels);
+ // Transform needed to consume the data:
+ result->norm_texture_from_norm_view = GetCameraUvFromScreenUvTransform();
+ result->size = gfx::Size(width, height);
+
+ DVLOG(3) << __func__ << ": norm_texture_from_norm_view=\n"
+ << result->norm_texture_from_norm_view.ToString();
+
+ previous_depth_data_time_ = time_delta;
+
+ return mojom::XRDepthData::NewUpdatedDepthData(std::move(result));
+ }
+
+ return mojom::XRDepthData::NewDataStillValid(
+ mojom::XRDepthDataStillValid::New());
+}
+
+bool ArCoreImpl::IsOnGlThread() const {
return gl_thread_task_runner_->BelongsToCurrentThread();
}
diff --git a/chromium/device/vr/android/arcore/arcore_impl.h b/chromium/device/vr/android/arcore/arcore_impl.h
index c6e51ff7bde..bc6f50dd8ea 100644
--- a/chromium/device/vr/android/arcore/arcore_impl.h
+++ b/chromium/device/vr/android/arcore/arcore_impl.h
@@ -107,12 +107,12 @@ class ArCoreImpl : public ArCore {
~ArCoreImpl() override;
bool Initialize(
- base::android::ScopedJavaLocalRef<jobject> application_context) override;
+ base::android::ScopedJavaLocalRef<jobject> application_context,
+ const std::unordered_set<device::mojom::XRSessionFeature>&
+ enabled_features) override;
void SetDisplayGeometry(const gfx::Size& frame_size,
display::Display::Rotation display_rotation) override;
void SetCameraTexture(uint32_t camera_texture_id) override;
- std::vector<float> TransformDisplayUvCoords(
- const base::span<const float> uvs) override;
gfx::Transform GetProjectionMatrix(float near, float far) override;
mojom::VRPosePtr Update(bool* camera_updated) override;
base::TimeDelta GetFrameTimestamp() override;
@@ -170,8 +170,14 @@ class ArCoreImpl : public ArCore {
void DetachAnchor(uint64_t anchor_id) override;
+ mojom::XRDepthDataPtr GetDepthData() override;
+
+ protected:
+ std::vector<float> TransformDisplayUvCoords(
+ const base::span<const float> uvs) const override;
+
private:
- bool IsOnGlThread();
+ bool IsOnGlThread() const;
base::WeakPtr<ArCoreImpl> GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
@@ -203,6 +209,11 @@ class ArCoreImpl : public ArCore {
std::vector<CreatePlaneAttachedAnchorRequest>
create_plane_attached_anchor_requests_;
+ // The time delta (relative to ARCore's depth data time base) of the last
+ // retrieved depth API data. Used to ensure that we do not return same data to
+ // the renderer if there were no changes.
+ base::TimeDelta previous_depth_data_time_;
+
HitTestSubscriptionId CreateHitTestSubscriptionId();
// Returns hit test subscription results for a single subscription given
diff --git a/chromium/device/vr/android/arcore/arcore_math_utils.cc b/chromium/device/vr/android/arcore/arcore_math_utils.cc
new file mode 100644
index 00000000000..5fc3b8744d9
--- /dev/null
+++ b/chromium/device/vr/android/arcore/arcore_math_utils.cc
@@ -0,0 +1,63 @@
+// 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/android/arcore/arcore_math_utils.h"
+
+#include "base/check_op.h"
+#include "base/logging.h"
+
+namespace device {
+
+gfx::Transform MatrixFromTransformedPoints(const base::span<const float> uvs) {
+ DCHECK_GE(uvs.size(), 6u);
+
+ //
+ // In order to compute the matrix, we need to solve the following 3 equations
+ // for 6 unknowns:
+ //
+ // | a b c | | u | | u' |
+ // | d e f | * | v | = | v' |
+ // | 0 0 1 | | 1 | | 1 |
+ //
+ // where 3 (u', v') pairs are passed in as an input to the method, and (u,v)
+ // pairs are assumed to come from kInputCoordinatesForTransform.
+ //
+ // 1. From substituting point (0, 0) for (u,v), we get:
+ //
+ // c = uvs[0]
+ // f = uvs[1]
+ //
+ // 2. From substituting point (1, 0) for (u,v), we get:
+ //
+ // a + c = uvs[2] -> a = uvs[2] - uvs[0]
+ // d + f = uvs[3] -> d = uvs[3] - uvs[1]
+ //
+ // 3. From substituting point (0, 1) for (u,v), we get:
+ //
+ // b + c = uvs[4] -> b = uvs[4] - uvs[0]
+ // e + f = uvs[5] -> e = uvs[5] - uvs[1]
+ //
+
+ DVLOG(3) << __func__ << ": uvs=[ " << uvs[0] << " , " << uvs[1] << " , "
+ << uvs[2] << " , " << uvs[3] << " , " << uvs[4] << " , " << uvs[5]
+ << " ]";
+
+ // Assumes that |uvs| is the result of transforming the display coordinates
+ // from kInputCoordinatesForTransform - size must match.
+ DCHECK_EQ(uvs.size(), kInputCoordinatesForTransform.size());
+
+ // Transform initializes to the identity matrix and then is modified by uvs.
+ gfx::Transform result;
+ result.matrix().set(0, 0, uvs[2] - uvs[0]);
+ result.matrix().set(0, 1, uvs[4] - uvs[0]);
+ result.matrix().set(0, 3, uvs[0]);
+
+ result.matrix().set(1, 0, uvs[3] - uvs[1]);
+ result.matrix().set(1, 1, uvs[5] - uvs[1]);
+ result.matrix().set(1, 3, uvs[1]);
+
+ return result;
+}
+
+} // namespace device
diff --git a/chromium/device/vr/android/arcore/arcore_math_utils.h b/chromium/device/vr/android/arcore/arcore_math_utils.h
new file mode 100644
index 00000000000..ca6701dae90
--- /dev/null
+++ b/chromium/device/vr/android/arcore/arcore_math_utils.h
@@ -0,0 +1,31 @@
+// 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_ANDROID_ARCORE_ARCORE_MATH_UTILS_H_
+#define DEVICE_VR_ANDROID_ARCORE_ARCORE_MATH_UTILS_H_
+
+#include <array>
+#include <vector>
+
+#include "base/containers/span.h"
+#include "ui/gfx/transform.h"
+
+namespace device {
+
+// Creates a matrix that transforms UV coordinates based on how a well known
+// input was transformed. ArCore doesn't provide a way to get a matrix directly.
+// There's a function to transform UV vectors individually, which can't be used
+// from a shader, so we run that on selected well-known vectors
+// (kDisplayCoordinatesForTransform) and recreate the matrix from the result.
+gfx::Transform MatrixFromTransformedPoints(const base::span<const float> uvs);
+
+// Input coordinates used when computing UV transform.
+// |MatrixFromTransformedPoints(uvs)| function above assumes that the |uvs| are
+// the result of transforming kInputCoordinatesForTransform by some matrix.
+constexpr std::array<float, 6> kInputCoordinatesForTransform = {0.f, 0.f, 1.f,
+ 0.f, 0.f, 1.f};
+
+} // namespace device
+
+#endif // DEVICE_VR_ANDROID_ARCORE_ARCORE_MATH_UTILS_H_
diff --git a/chromium/device/vr/android/arcore/arcore_session_utils.h b/chromium/device/vr/android/arcore/arcore_session_utils.h
new file mode 100644
index 00000000000..d3c3f4d3d03
--- /dev/null
+++ b/chromium/device/vr/android/arcore/arcore_session_utils.h
@@ -0,0 +1,54 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_VR_ANDROID_ARCORE_ARCORE_SESSION_UTILS_H_
+#define DEVICE_VR_ANDROID_ARCORE_ARCORE_SESSION_UTILS_H_
+
+#include "base/android/scoped_java_ref.h"
+#include "base/memory/weak_ptr.h"
+#include "ui/display/display.h"
+#include "ui/gfx/geometry/point_f.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/native_widget_types.h"
+
+namespace vr {
+
+// Immersive AR sessions use callbacks in the following sequence:
+//
+// RequestArSession
+// SurfaceReadyCallback
+// SurfaceTouchCallback (repeated for each touch)
+// [exit session via "back" button, or via JS session exit]
+// DestroyedCallback
+//
+using SurfaceReadyCallback =
+ base::RepeatingCallback<void(gfx::AcceleratedWidget window,
+ display::Display::Rotation rotation,
+ const gfx::Size& size)>;
+using SurfaceTouchCallback =
+ base::RepeatingCallback<void(bool is_primary,
+ bool touching,
+ int32_t pointer_id,
+ const gfx::PointF& location)>;
+using SurfaceDestroyedCallback = base::OnceClosure;
+
+class ArCoreSessionUtils {
+ public:
+ virtual ~ArCoreSessionUtils() = default;
+ virtual bool EnsureLoaded() = 0;
+ virtual base::android::ScopedJavaLocalRef<jobject>
+ GetApplicationContext() = 0;
+ virtual void RequestArSession(
+ int render_process_id,
+ int render_frame_id,
+ bool use_overlay,
+ SurfaceReadyCallback ready_callback,
+ SurfaceTouchCallback touch_callback,
+ SurfaceDestroyedCallback destroyed_callback) = 0;
+ virtual void EndSession() = 0;
+};
+
+} // namespace vr
+
+#endif // DEVICE_VR_ANDROID_ARCORE_ARCORE_SESSION_UTILS_H_
diff --git a/chromium/device/vr/android/arcore/scoped_arcore_objects.h b/chromium/device/vr/android/arcore/scoped_arcore_objects.h
index 0d47da47890..6c8a08b7d46 100644
--- a/chromium/device/vr/android/arcore/scoped_arcore_objects.h
+++ b/chromium/device/vr/android/arcore/scoped_arcore_objects.h
@@ -69,6 +69,11 @@ void inline ScopedGenericArObject<ArPlane*>::Free(ArPlane* ar_plane) {
}
template <>
+void inline ScopedGenericArObject<ArImage*>::Free(ArImage* ar_image) {
+ ArImage_release(ar_image);
+}
+
+template <>
void inline ScopedGenericArObject<ArAnchor*>::Free(ArAnchor* ar_anchor) {
ArAnchor_release(ar_anchor);
}
diff --git a/chromium/device/vr/android/gvr/gvr_device.cc b/chromium/device/vr/android/gvr/gvr_device.cc
index 1dff51894ef..a4e25c155f3 100644
--- a/chromium/device/vr/android/gvr/gvr_device.cc
+++ b/chromium/device/vr/android/gvr/gvr_device.cc
@@ -32,17 +32,6 @@ namespace device {
namespace {
-// Default downscale factor for computing the recommended WebXR
-// render_width/render_height from the 1:1 pixel mapped size. Using a rather
-// aggressive downscale due to the high overhead of copying pixels
-// twice before handing off to GVR. For comparison, the polyfill
-// uses approximately 0.55 on a Pixel XL.
-static constexpr float kWebXrRecommendedResolutionScale = 0.7;
-
-// The scale factor for WebXR on devices that don't have shared buffer
-// support. (Android N and earlier.)
-static constexpr float kWebXrNoSharedBufferResolutionScale = 0.5;
-
gfx::Size GetMaximumWebVrSize(gvr::GvrApi* gvr_api) {
// Get the default, unscaled size for the WebVR transfer surface
// based on the optimal 1:1 render resolution. A scalar will be applied to
@@ -98,14 +87,11 @@ mojom::VREyeParametersPtr CreateEyeParamater(
return eye_params;
}
-mojom::VRDisplayInfoPtr CreateVRDisplayInfo(gvr::GvrApi* gvr_api,
- mojom::XRDeviceId device_id) {
+mojom::VRDisplayInfoPtr CreateVRDisplayInfo(gvr::GvrApi* gvr_api) {
TRACE_EVENT0("input", "GvrDelegate::CreateVRDisplayInfo");
mojom::VRDisplayInfoPtr device = mojom::VRDisplayInfo::New();
- device->id = device_id;
-
gvr::BufferViewportList gvr_buffer_viewports =
gvr_api->CreateEmptyBufferViewportList();
gvr_buffer_viewports.SetToRecommendedBufferViewports();
@@ -116,16 +102,6 @@ mojom::VRDisplayInfoPtr CreateVRDisplayInfo(gvr::GvrApi* gvr_api,
device->right_eye = CreateEyeParamater(gvr_api, GVR_RIGHT_EYE,
gvr_buffer_viewports, maximum_size);
- // This scalar will be applied in the renderer to the recommended render
- // target sizes. For WebVR it will always be applied, for WebXR it can be
- // overridden.
- if (base::AndroidHardwareBufferCompat::IsSupportAvailable()) {
- device->webxr_default_framebuffer_scale = kWebXrRecommendedResolutionScale;
- } else {
- device->webxr_default_framebuffer_scale =
- kWebXrNoSharedBufferResolutionScale;
- }
-
return device;
}
@@ -270,7 +246,7 @@ GvrDelegateProvider* GvrDevice::GetGvrDelegateProvider() {
void GvrDevice::OnDisplayConfigurationChanged(JNIEnv* env,
const JavaRef<jobject>& obj) {
DCHECK(gvr_api_);
- SetVRDisplayInfo(CreateVRDisplayInfo(gvr_api_.get(), GetId()));
+ SetVRDisplayInfo(CreateVRDisplayInfo(gvr_api_.get()));
}
void GvrDevice::Init(base::OnceCallback<void(bool)> on_finished) {
@@ -294,7 +270,7 @@ void GvrDevice::CreateNonPresentingContext() {
jlong context = Java_NonPresentingGvrContext_getNativeGvrContext(
env, non_presenting_context_);
gvr_api_ = gvr::GvrApi::WrapNonOwned(reinterpret_cast<gvr_context*>(context));
- SetVRDisplayInfo(CreateVRDisplayInfo(gvr_api_.get(), GetId()));
+ SetVRDisplayInfo(CreateVRDisplayInfo(gvr_api_.get()));
if (paused_) {
PauseTracking();
diff --git a/chromium/device/vr/android/java/DEPS b/chromium/device/vr/android/java/DEPS
deleted file mode 100644
index 05f395ef21d..00000000000
--- a/chromium/device/vr/android/java/DEPS
+++ /dev/null
@@ -1,3 +0,0 @@
-include_rules = [
- "+ui/android/java",
-]
diff --git a/chromium/device/vr/android/java/src/org/chromium/device/vr/NonPresentingGvrContext.java b/chromium/device/vr/android/java/src/org/chromium/device/vr/NonPresentingGvrContext.java
deleted file mode 100644
index bb31aecfe74..00000000000
--- a/chromium/device/vr/android/java/src/org/chromium/device/vr/NonPresentingGvrContext.java
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.device.vr;
-
-import android.content.Context;
-import android.view.Display;
-
-import com.google.vr.cardboard.DisplaySynchronizer;
-import com.google.vr.ndk.base.GvrApi;
-
-import org.chromium.base.ContextUtils;
-import org.chromium.base.StrictModeContext;
-import org.chromium.base.annotations.CalledByNative;
-import org.chromium.base.annotations.JNINamespace;
-import org.chromium.base.annotations.NativeMethods;
-import org.chromium.ui.display.DisplayAndroidManager;
-
-/**
- * Creates an active GvrContext from a GvrApi created from the Application Context. This GvrContext
- * cannot be used for VR rendering, and should only be used to query pose information and device
- * parameters.
- */
-@JNINamespace("device")
-public class NonPresentingGvrContext {
- private GvrApi mGvrApi;
- private DisplaySynchronizer mDisplaySynchronizer;
- private boolean mResumed;
-
- private long mNativeGvrDevice;
-
- private NonPresentingGvrContext(long nativeGvrDevice) {
- mNativeGvrDevice = nativeGvrDevice;
- Context context = ContextUtils.getApplicationContext();
- Display display = DisplayAndroidManager.getDefaultDisplayForContext(context);
-
- try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
- mDisplaySynchronizer = new DisplaySynchronizer(context, display) {
- @Override
- public void onConfigurationChanged() {
- super.onConfigurationChanged();
- onDisplayConfigurationChanged();
- }
- };
- }
-
- // Creating the GvrApi can sometimes create the Daydream config file.
- try (StrictModeContext ignored = StrictModeContext.allowDiskWrites()) {
- mGvrApi = new GvrApi(context, mDisplaySynchronizer);
- }
- resume();
- }
-
- @CalledByNative
- private static NonPresentingGvrContext create(long nativeNonPresentingGvrContext) {
- try {
- return new NonPresentingGvrContext(nativeNonPresentingGvrContext);
- } catch (IllegalStateException | UnsatisfiedLinkError e) {
- return null;
- }
- }
-
- @CalledByNative
- private long getNativeGvrContext() {
- return mGvrApi.getNativeGvrContext();
- }
-
- @CalledByNative
- private void pause() {
- if (!mResumed) return;
- mResumed = false;
- mDisplaySynchronizer.onPause();
- }
-
- @CalledByNative
- private void resume() {
- if (mResumed) return;
- mResumed = true;
- mDisplaySynchronizer.onResume();
- }
-
- @CalledByNative
- private void shutdown() {
- mDisplaySynchronizer.shutdown();
- mGvrApi.shutdown();
- mNativeGvrDevice = 0;
- }
-
- public void onDisplayConfigurationChanged() {
- mGvrApi.refreshDisplayMetrics();
- if (mNativeGvrDevice != 0) {
- NonPresentingGvrContextJni.get().onDisplayConfigurationChanged(
- mNativeGvrDevice, NonPresentingGvrContext.this);
- }
- }
-
- @NativeMethods
- interface Natives {
- void onDisplayConfigurationChanged(long nativeGvrDevice, NonPresentingGvrContext caller);
- }
-}
diff --git a/chromium/device/vr/android/mailbox_to_surface_bridge.h b/chromium/device/vr/android/mailbox_to_surface_bridge.h
new file mode 100644
index 00000000000..493d696d124
--- /dev/null
+++ b/chromium/device/vr/android/mailbox_to_surface_bridge.h
@@ -0,0 +1,97 @@
+// 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_ANDROID_MAILBOX_TO_SURFACE_BRIDGE_H_
+#define DEVICE_VR_ANDROID_MAILBOX_TO_SURFACE_BRIDGE_H_
+
+namespace gfx {
+class ColorSpace;
+class GpuFence;
+} // namespace gfx
+
+namespace gl {
+class SurfaceTexture;
+} // namespace gl
+
+namespace gpu {
+class GpuMemoryBufferImplAndroidHardwareBuffer;
+struct MailboxHolder;
+struct SyncToken;
+} // namespace gpu
+
+namespace device {
+class MailboxToSurfaceBridge {
+ public:
+ virtual ~MailboxToSurfaceBridge() {}
+
+ // Returns true if the GPU process connection is established and ready to use.
+ // Equivalent to waiting for on_initialized to be called.
+ virtual bool IsConnected() = 0;
+
+ // Checks if a workaround from "gpu/config/gpu_driver_bug_workaround_type.h"
+ // is active. Requires initialization to be complete.
+ virtual bool IsGpuWorkaroundEnabled(int32_t workaround) = 0;
+
+ // This call is needed for Surface transport, in that case it must be called
+ // on the GL thread with a valid local native GL context. If it's not used,
+ // only the SharedBuffer transport methods are available.
+ virtual void CreateSurface(gl::SurfaceTexture*) = 0;
+
+ // Asynchronously create the context using the surface provided by an earlier
+ // CreateSurface call, or an offscreen context if that wasn't called. Also
+ // binds the context provider to the current thread (making it the GL thread),
+ // and calls the callback on the GL thread.
+ virtual void CreateAndBindContextProvider(base::OnceClosure callback) = 0;
+
+ // All other public methods below must be called on the GL thread
+ // (except when marked otherwise).
+
+ virtual void ResizeSurface(int width, int height) = 0;
+
+ // Returns true if swapped successfully. This can fail if the GL
+ // context isn't ready for use yet, in that case the caller
+ // won't get a new frame on the SurfaceTexture.
+ virtual bool CopyMailboxToSurfaceAndSwap(
+ const gpu::MailboxHolder& mailbox) = 0;
+
+ virtual void GenSyncToken(gpu::SyncToken* out_sync_token) = 0;
+
+ virtual void WaitSyncToken(const gpu::SyncToken& sync_token) = 0;
+
+ // Copies a GpuFence from the local context to the GPU process,
+ // and issues a server wait for it.
+ virtual void WaitForClientGpuFence(gfx::GpuFence*) = 0;
+
+ // Creates a GpuFence in the GPU process after the supplied sync_token
+ // completes, and copies it for use in the local context. This is
+ // asynchronous, the callback receives the GpuFence once it's available.
+ virtual void CreateGpuFence(
+ const gpu::SyncToken& sync_token,
+ base::OnceCallback<void(std::unique_ptr<gfx::GpuFence>)> callback) = 0;
+
+ // Creates a shared image bound to |buffer|. Returns a mailbox holder that
+ // references the shared image with a sync token representing a point after
+ // the creation. Caller must call DestroySharedImage to free the shared image.
+ // Does not take ownership of |buffer| or retain any references to it.
+ virtual gpu::MailboxHolder CreateSharedImage(
+ gpu::GpuMemoryBufferImplAndroidHardwareBuffer* buffer,
+ const gfx::ColorSpace& color_space,
+ uint32_t usage) = 0;
+
+ // Destroys a shared image created by CreateSharedImage. The mailbox_holder's
+ // sync_token must have been updated to a sync token after the last use of the
+ // shared image.
+ virtual void DestroySharedImage(const gpu::MailboxHolder& mailbox_holder) = 0;
+};
+
+class MailboxToSurfaceBridgeFactory {
+ public:
+ virtual ~MailboxToSurfaceBridgeFactory() {}
+
+ virtual std::unique_ptr<device::MailboxToSurfaceBridge> Create() const = 0;
+};
+
+} // namespace device
+
+#endif // DEVICE_VR_ANDROID_MAILBOX_TO_SURFACE_BRIDGE_H_
diff --git a/chromium/device/vr/android/web_xr_presentation_state.cc b/chromium/device/vr/android/web_xr_presentation_state.cc
new file mode 100644
index 00000000000..7c42a6d8571
--- /dev/null
+++ b/chromium/device/vr/android/web_xr_presentation_state.cc
@@ -0,0 +1,184 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/vr/android/web_xr_presentation_state.h"
+
+#include "base/logging.h"
+#include "base/trace_event/trace_event.h"
+#include "gpu/ipc/common/gpu_memory_buffer_impl_android_hardware_buffer.h"
+#include "ui/gl/gl_fence.h"
+#include "ui/gl/gl_image_egl.h"
+
+namespace vr {
+
+WebXrSharedBuffer::WebXrSharedBuffer() = default;
+WebXrSharedBuffer::~WebXrSharedBuffer() = default;
+
+WebXrFrame::WebXrFrame() = default;
+
+WebXrFrame::~WebXrFrame() = default;
+
+bool WebXrFrame::IsValid() {
+ return index >= 0;
+}
+
+void WebXrFrame::Recycle() {
+ DCHECK(!state_locked);
+ index = -1;
+ deferred_start_processing.Reset();
+ recycle_once_unlocked = false;
+ gvr_handoff_fence.reset();
+}
+
+WebXrPresentationState::WebXrPresentationState() {
+ for (auto& frame : frames_storage_) {
+ // Create frames in "idle" state.
+ frame = std::make_unique<WebXrFrame>();
+ idle_frames_.push(frame.get());
+ }
+}
+
+WebXrPresentationState::~WebXrPresentationState() {}
+
+WebXrFrame* WebXrPresentationState::GetAnimatingFrame() {
+ DCHECK(HaveAnimatingFrame());
+ DCHECK(animating_frame_->IsValid());
+ return animating_frame_;
+}
+
+WebXrFrame* WebXrPresentationState::GetProcessingFrame() {
+ DCHECK(HaveProcessingFrame());
+ DCHECK(processing_frame_->IsValid());
+ return processing_frame_;
+}
+
+WebXrFrame* WebXrPresentationState::GetRenderingFrame() {
+ DCHECK(HaveRenderingFrame());
+ DCHECK(rendering_frame_->IsValid());
+ return rendering_frame_;
+}
+
+WebXrPresentationState::FrameIndexType
+WebXrPresentationState::StartFrameAnimating() {
+ DCHECK(!HaveAnimatingFrame());
+ DCHECK(!idle_frames_.empty());
+ animating_frame_ = idle_frames_.front();
+ idle_frames_.pop();
+ animating_frame_->index = next_frame_index_++;
+ return animating_frame_->index;
+}
+
+void WebXrPresentationState::TransitionFrameAnimatingToProcessing() {
+ DCHECK(HaveAnimatingFrame());
+ DCHECK(animating_frame_->IsValid());
+ DCHECK(!animating_frame_->state_locked);
+ DCHECK(!HaveProcessingFrame());
+ processing_frame_ = animating_frame_;
+ animating_frame_ = nullptr;
+}
+
+void WebXrPresentationState::RecycleUnusedAnimatingFrame() {
+ DCHECK(HaveAnimatingFrame());
+ animating_frame_->Recycle();
+ idle_frames_.push(animating_frame_);
+ animating_frame_ = nullptr;
+}
+
+void WebXrPresentationState::TransitionFrameProcessingToRendering() {
+ DCHECK(HaveProcessingFrame());
+ DCHECK(processing_frame_->IsValid());
+ DCHECK(!processing_frame_->state_locked);
+ DCHECK(!HaveRenderingFrame());
+ rendering_frame_ = processing_frame_;
+ processing_frame_ = nullptr;
+}
+
+void WebXrPresentationState::EndFrameRendering() {
+ DCHECK(HaveRenderingFrame());
+ DCHECK(rendering_frame_->IsValid());
+ rendering_frame_->Recycle();
+ idle_frames_.push(rendering_frame_);
+ rendering_frame_ = nullptr;
+}
+
+bool WebXrPresentationState::RecycleProcessingFrameIfPossible() {
+ DCHECK(HaveProcessingFrame());
+ bool can_cancel = !processing_frame_->state_locked;
+ if (can_cancel) {
+ processing_frame_->Recycle();
+ idle_frames_.push(processing_frame_);
+ processing_frame_ = nullptr;
+ } else {
+ processing_frame_->recycle_once_unlocked = true;
+ }
+ return can_cancel;
+}
+
+std::vector<std::unique_ptr<WebXrSharedBuffer>>
+WebXrPresentationState::TakeSharedBuffers() {
+ std::vector<std::unique_ptr<WebXrSharedBuffer>> shared_buffers;
+ for (auto& frame : frames_storage_) {
+ if (frame->shared_buffer)
+ shared_buffers.emplace_back(std::move(frame->shared_buffer));
+ if (frame->camera_image_shared_buffer)
+ shared_buffers.emplace_back(std::move(frame->camera_image_shared_buffer));
+ }
+ return shared_buffers;
+}
+
+void WebXrPresentationState::EndPresentation() {
+ TRACE_EVENT0("gpu", __FUNCTION__);
+
+ if (HaveRenderingFrame()) {
+ rendering_frame_->Recycle();
+ idle_frames_.push(rendering_frame_);
+ rendering_frame_ = nullptr;
+ }
+ if (HaveProcessingFrame()) {
+ RecycleProcessingFrameIfPossible();
+ }
+ if (HaveAnimatingFrame()) {
+ RecycleUnusedAnimatingFrame();
+ }
+
+ last_ui_allows_sending_vsync = false;
+}
+
+bool WebXrPresentationState::CanProcessFrame() const {
+ if (!mailbox_bridge_ready_) {
+ DVLOG(2) << __FUNCTION__ << ": waiting for mailbox bridge";
+ return false;
+ }
+ if (processing_frame_) {
+ DVLOG(2) << __FUNCTION__ << ": waiting for previous processing frame";
+ return false;
+ }
+
+ return true;
+}
+
+void WebXrPresentationState::ProcessOrDefer(base::OnceClosure callback) {
+ DCHECK(animating_frame_ && !animating_frame_->deferred_start_processing);
+ if (CanProcessFrame()) {
+ TransitionFrameAnimatingToProcessing();
+ std::move(callback).Run();
+ } else {
+ DVLOG(2) << "Deferring processing frame, not ready";
+ animating_frame_->deferred_start_processing = std::move(callback);
+ }
+}
+
+void WebXrPresentationState::TryDeferredProcessing() {
+ if (!animating_frame_ || !animating_frame_->deferred_start_processing ||
+ !CanProcessFrame()) {
+ return;
+ }
+ DVLOG(2) << "Running deferred SubmitFrame";
+ // Run synchronously, not via PostTask, to ensure we don't
+ // get a new SendVSync scheduling in between.
+ TransitionFrameAnimatingToProcessing();
+ std::move(animating_frame_->deferred_start_processing).Run();
+}
+
+} // namespace vr
diff --git a/chromium/device/vr/android/web_xr_presentation_state.h b/chromium/device/vr/android/web_xr_presentation_state.h
new file mode 100644
index 00000000000..a13e1a1a725
--- /dev/null
+++ b/chromium/device/vr/android/web_xr_presentation_state.h
@@ -0,0 +1,217 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_VR_ANDROID_WEB_XR_PRESENTATION_STATE_H_
+#define DEVICE_VR_ANDROID_WEB_XR_PRESENTATION_STATE_H_
+
+#include <memory>
+#include <utility>
+
+#include "base/callback.h"
+#include "base/containers/queue.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "gpu/command_buffer/common/mailbox_holder.h"
+#include "ui/gfx/geometry/size.h"
+#include "ui/gfx/transform.h"
+
+namespace gl {
+class GLFence;
+class GLImageEGL;
+} // namespace gl
+
+namespace gpu {
+class GpuMemoryBufferImplAndroidHardwareBuffer;
+} // namespace gpu
+
+namespace vr {
+// WebVR/WebXR frames go through a three-stage pipeline: Animating, Processing,
+// and Rendering. There's also an Idle state used as the starting state before
+// Animating and ending state after Rendering.
+//
+// The stages can overlap, but we enforce that there isn't more than one
+// frame in a given non-Idle state at any one time.
+//
+// <- GetFrameData
+// Idle
+// SendVSync
+// Animating
+// <- UpdateLayerBounds (optional)
+// <- GetFrameData
+// <- SubmitFrame
+// ProcessWebVrFrame
+// Processing
+// <- OnWebVrFrameAvailable
+// DrawFrame
+// DrawFrameSubmitWhenReady
+// <= poll prev_frame_completion_fence_
+// DrawFrameSubmitNow
+// Rendering
+// <= prev_frame_completion_fence_ signals
+// DrawFrameSubmitNow (of next frame)
+// Idle
+//
+// Note that the frame is considered to still be in "Animating" state until
+// ProcessWebVrFrame is called. If the current processing frame isn't done yet
+// at the time the incoming SubmitFrame arrives, we defer ProcessWebVrFrame
+// until that finishes.
+//
+// The renderer may call SubmitFrameMissing instead of SubmitFrame. In that
+// case, the frame transitions from Animating back to Idle.
+//
+// <- GetFrameData
+// Idle
+// SendVSync
+// Animating
+// <- UpdateLayerBounds (optional)
+// <- GetFrameData
+// <- SubmitFrameMissing
+// Idle
+
+struct WebXrSharedBuffer {
+ WebXrSharedBuffer();
+ ~WebXrSharedBuffer();
+
+ gfx::Size size = {0, 0};
+
+ // Shared GpuMemoryBuffer
+ std::unique_ptr<gpu::GpuMemoryBufferImplAndroidHardwareBuffer> gmb;
+
+ // Resources in the remote GPU process command buffer context
+ gpu::MailboxHolder mailbox_holder;
+
+ // Resources in the local GL context
+ uint32_t local_texture = 0;
+ // This refptr keeps the image alive while processing a frame. That's
+ // required because it owns underlying resources, and must still be
+ // alive when the mailbox texture backed by this image is used.
+ scoped_refptr<gl::GLImageEGL> local_glimage;
+};
+
+struct WebXrFrame {
+ WebXrFrame();
+ ~WebXrFrame();
+
+ bool IsValid();
+ void Recycle();
+
+ // If true, this frame cannot change state until unlocked. Used to mark
+ // processing frames for the critical stage from drawing to Surface until
+ // they arrive in OnWebVRFrameAvailable. See also recycle_once_unlocked.
+ bool state_locked = false;
+
+ // Start of elements that need to be reset on Recycle
+
+ int16_t index = -1;
+
+ // Set on an animating frame if it is waiting for being able to transition
+ // to processing state.
+ base::OnceClosure deferred_start_processing;
+
+ // Set if a frame recycle failed due to being locked. The client should check
+ // this after unlocking it and retry recycling it at that time.
+ bool recycle_once_unlocked = false;
+
+ std::unique_ptr<gl::GLFence> gvr_handoff_fence;
+
+ // End of elements that need to be reset on Recycle
+
+ base::TimeTicks time_pose;
+ base::TimeTicks time_js_submit;
+ base::TimeTicks time_copied;
+ gfx::Transform head_pose;
+
+ // In SharedBuffer mode, keep a swap chain.
+ std::unique_ptr<WebXrSharedBuffer> shared_buffer;
+
+ std::unique_ptr<WebXrSharedBuffer> camera_image_shared_buffer;
+
+ DISALLOW_COPY_AND_ASSIGN(WebXrFrame);
+};
+
+class WebXrPresentationState {
+ public:
+ // WebXR frames use an arbitrary sequential ID to help catch logic errors
+ // involving out-of-order frames. We use an 8-bit unsigned counter, wrapping
+ // from 255 back to 0. Elsewhere we use -1 to indicate a non-WebXR frame, so
+ // most internal APIs use int16_t to ensure that they can store a full
+ // -1..255 value range.
+ using FrameIndexType = uint8_t;
+
+ // We have at most one frame animating, one frame being processed,
+ // and one frame tracked after submission to GVR.
+ static constexpr int kWebXrFrameCount = 3;
+
+ WebXrPresentationState();
+ ~WebXrPresentationState();
+
+ // State transitions for normal flow
+ FrameIndexType StartFrameAnimating();
+ void TransitionFrameAnimatingToProcessing();
+ void TransitionFrameProcessingToRendering();
+ void EndFrameRendering();
+
+ // Shuts down a presentation session. This will recycle any
+ // animating or rendering frame. A processing frame cannot be
+ // recycled if its state is locked, it will be recycled later
+ // once the state unlocks.
+ void EndPresentation();
+
+ // Variant transitions, if Renderer didn't call SubmitFrame,
+ // or if we want to discard an unwanted incoming frame.
+ void RecycleUnusedAnimatingFrame();
+ bool RecycleProcessingFrameIfPossible();
+
+ void ProcessOrDefer(base::OnceClosure callback);
+ // Call this after state changes that could result in CanProcessFrame
+ // becoming true.
+ void TryDeferredProcessing();
+
+ bool HaveAnimatingFrame() const { return animating_frame_; }
+ WebXrFrame* GetAnimatingFrame();
+ bool HaveProcessingFrame() const { return processing_frame_; }
+ WebXrFrame* GetProcessingFrame();
+ bool HaveRenderingFrame() const { return rendering_frame_; }
+ WebXrFrame* GetRenderingFrame();
+
+ bool mailbox_bridge_ready() { return mailbox_bridge_ready_; }
+ void NotifyMailboxBridgeReady() { mailbox_bridge_ready_ = true; }
+
+ // Extracts the shared buffers from all frames, resetting said frames to an
+ // invalid state.
+ // This is intended for resource cleanup, after EndPresentation was called.
+ std::vector<std::unique_ptr<WebXrSharedBuffer>> TakeSharedBuffers();
+
+ // Used by WebVrCanAnimateFrame() to detect when ui_->CanSendWebVrVSync()
+ // transitions from false to true, as part of starting the incoming frame
+ // timeout.
+ bool last_ui_allows_sending_vsync = false;
+
+ // GpuMemoryBuffer creation needs a buffer ID. We don't really care about
+ // this, but try to keep it unique to avoid confusion.
+ int next_memory_buffer_id = 0;
+
+ private:
+ // Checks if we're in a valid state for processing the current animating
+ // frame. Invalid states include mailbox_bridge_ready_ being false, or an
+ // already existing processing frame that's not done yet.
+ bool CanProcessFrame() const;
+ std::unique_ptr<WebXrFrame> frames_storage_[kWebXrFrameCount];
+
+ // Index of the next animating WebXR frame.
+ FrameIndexType next_frame_index_ = 0;
+
+ WebXrFrame* animating_frame_ = nullptr;
+ WebXrFrame* processing_frame_ = nullptr;
+ WebXrFrame* rendering_frame_ = nullptr;
+ base::queue<WebXrFrame*> idle_frames_;
+
+ bool mailbox_bridge_ready_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(WebXrPresentationState);
+};
+
+} // namespace vr
+
+#endif // DEVICE_VR_ANDROID_WEB_XR_PRESENTATION_STATE_H_
diff --git a/chromium/device/vr/buildflags/BUILD.gn b/chromium/device/vr/buildflags/BUILD.gn
index 91b93e56834..7fcb22389c7 100644
--- a/chromium/device/vr/buildflags/BUILD.gn
+++ b/chromium/device/vr/buildflags/BUILD.gn
@@ -11,7 +11,6 @@ buildflag_header("buildflags") {
flags = [
"ENABLE_ARCORE=$enable_arcore",
"ENABLE_OCULUS_VR=$enable_oculus_vr",
- "ENABLE_OPENVR=$enable_openvr",
"ENABLE_VR=$enable_vr",
"ENABLE_WINDOWS_MR=$enable_windows_mr",
"ENABLE_OPENXR=$enable_openxr",
diff --git a/chromium/device/vr/buildflags/buildflags.gni b/chromium/device/vr/buildflags/buildflags.gni
index d85ca83da71..46792daad3a 100644
--- a/chromium/device/vr/buildflags/buildflags.gni
+++ b/chromium/device/vr/buildflags/buildflags.gni
@@ -10,10 +10,10 @@ declare_args() {
enable_gvr_services = is_android && !is_chromecast &&
(current_cpu == "arm" || current_cpu == "arm64")
- enable_openvr = is_win
-
enable_windows_mr = is_win
+ use_command_buffer = is_win
+
# To build with OpenXR support, the OpenXR Loader needs to be pulled to
# third_party/openxr.
enable_openxr = checkout_openxr && is_win
@@ -28,8 +28,8 @@ declare_args() {
# Enable VR device support whenever VR device SDK(s) are supported.
# We enable VR on Linux even though VR features aren't usable because
# the binary size impact is small and allows many VR tests to run on Linux
- enable_vr = enable_gvr_services || enable_openvr || enable_oculus_vr ||
- enable_windows_mr || enable_openxr ||
+ enable_vr = enable_gvr_services || enable_oculus_vr || enable_windows_mr ||
+ enable_openxr ||
(is_desktop_linux &&
(current_cpu == "x64" || current_cpu == "x86") && !is_chromecast)
diff --git a/chromium/device/vr/gl_bindings.h b/chromium/device/vr/gl_bindings.h
new file mode 100644
index 00000000000..b727fdd4149
--- /dev/null
+++ b/chromium/device/vr/gl_bindings.h
@@ -0,0 +1,26 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_VR_GL_BINDINGS_H_
+#define DEVICE_VR_GL_BINDINGS_H_
+
+#if defined(VR_USE_COMMAND_BUFFER)
+
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#elif defined(VR_USE_NATIVE_GL)
+
+#include "ui/gl/gl_bindings.h" // nogncheck
+
+// The above header still uses the ARB prefix for the following GL API call.
+#define glGenBuffers glGenBuffersARB
+
+#else
+
+#error "Missing configuration for GL mode."
+
+#endif // defined(VR_USE_COMMAND_BUFFER)
+
+#endif // DEVICE_VR_GL_BINDINGS_H_
diff --git a/chromium/device/vr/oculus/oculus_device.cc b/chromium/device/vr/oculus/oculus_device.cc
index 1aa88489ece..252c26ace02 100644
--- a/chromium/device/vr/oculus/oculus_device.cc
+++ b/chromium/device/vr/oculus/oculus_device.cc
@@ -59,10 +59,8 @@ mojom::VREyeParametersPtr GetEyeDetails(ovrSession session,
return eye_parameters;
}
-mojom::VRDisplayInfoPtr CreateVRDisplayInfo(mojom::XRDeviceId id,
- ovrSession session) {
+mojom::VRDisplayInfoPtr CreateVRDisplayInfo(ovrSession session) {
mojom::VRDisplayInfoPtr display_info = mojom::VRDisplayInfo::New();
- display_info->id = id;
ovrHmdDesc hmdDesc = ovr_GetHmdDesc(session);
display_info->left_eye = GetEyeDetails(session, hmdDesc, ovrEye_Left);
@@ -174,7 +172,7 @@ bool OculusDevice::EnsureValidDisplayInfo() {
return false;
}
- SetVRDisplayInfo(CreateVRDisplayInfo(GetId(), session_));
+ SetVRDisplayInfo(CreateVRDisplayInfo(session_));
have_real_display_info_ = true;
}
return have_real_display_info_;
diff --git a/chromium/device/vr/openvr/OWNERS b/chromium/device/vr/openvr/OWNERS
deleted file mode 100644
index 5b1397a961f..00000000000
--- a/chromium/device/vr/openvr/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-per-file *_type_converter*.*=set noparent
-per-file *_type_converter*.*=file://ipc/SECURITY_OWNERS
-
-# TEAM: xr-dev@chromium.org
-# COMPONENT: Internals>XR>VR
diff --git a/chromium/device/vr/openvr/openvr_api_wrapper.cc b/chromium/device/vr/openvr/openvr_api_wrapper.cc
deleted file mode 100644
index 9a48eeb3225..00000000000
--- a/chromium/device/vr/openvr/openvr_api_wrapper.cc
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "device/vr/openvr/openvr_api_wrapper.h"
-
-#include "base/single_thread_task_runner.h"
-#include "base/threading/thread_task_runner_handle.h"
-#include "device/vr/test/test_hook.h"
-
-namespace device {
-
-OpenVRWrapper::OpenVRWrapper(bool for_rendering) {
- initialized_ = Initialize(for_rendering);
-}
-
-OpenVRWrapper::~OpenVRWrapper() {
- if (initialized_)
- Uninitialize();
-}
-
-vr::IVRCompositor* OpenVRWrapper::GetCompositor() {
- DCHECK(current_task_runner_->BelongsToCurrentThread());
- return compositor_;
-}
-
-vr::IVRSystem* OpenVRWrapper::GetSystem() {
- DCHECK(current_task_runner_->BelongsToCurrentThread());
- return system_;
-}
-
-void OpenVRWrapper::SetTestHook(VRTestHook* hook) {
- // This may be called from any thread - tests are responsible for
- // maintaining thread safety, typically by not changing the test hook
- // while presenting.
- test_hook_ = hook;
- if (service_test_hook_) {
- service_test_hook_->SetTestHook(test_hook_);
- }
-}
-
-bool OpenVRWrapper::Initialize(bool for_rendering) {
- DCHECK(!any_initialized_);
- any_initialized_ = true;
-
- // device can only be used on this thread once initailized
- vr::EVRInitError init_error = vr::VRInitError_None;
- system_ =
- vr::VR_Init(&init_error, vr::EVRApplicationType::VRApplication_Scene);
-
- if (init_error != vr::VRInitError_None) {
- LOG(ERROR) << vr::VR_GetVRInitErrorAsEnglishDescription(init_error);
- any_initialized_ = false;
- return false;
- }
-
- current_task_runner_ = base::ThreadTaskRunnerHandle::Get();
-
- if (for_rendering) {
- compositor_ = vr::VRCompositor();
- }
-
- if (test_hook_) {
- // Allow our mock implementation of OpenVR to be controlled by tests.
- // Note that SetTestHook must be called before CreateDevice, or
- // service_test_hook_s will remain null. This is a good pattern for
- // tests anyway, since the alternative is we start mocking part-way through
- // using the device, and end up with race conditions for when we started
- // controlling things.
- vr::EVRInitError eError;
- service_test_hook_ = static_cast<ServiceTestHook*>(
- vr::VR_GetGenericInterface(kChromeOpenVRTestHookAPI, &eError));
- if (service_test_hook_) {
- service_test_hook_->SetTestHook(test_hook_);
- test_hook_->AttachCurrentThread();
- }
- }
-
- return true;
-}
-
-void OpenVRWrapper::Uninitialize() {
- DCHECK(initialized_);
- initialized_ = false;
- system_ = nullptr;
- compositor_ = nullptr;
- service_test_hook_ = nullptr;
- current_task_runner_ = nullptr;
- if (test_hook_)
- test_hook_->DetachCurrentThread();
- vr::VR_Shutdown();
-
- any_initialized_ = false;
-}
-
-VRTestHook* OpenVRWrapper::test_hook_ = nullptr;
-bool OpenVRWrapper::any_initialized_ = false;
-ServiceTestHook* OpenVRWrapper::service_test_hook_ = nullptr;
-
-std::string GetOpenVRString(vr::IVRSystem* vr_system,
- vr::TrackedDeviceProperty prop,
- uint32_t device_index) {
- std::string out;
-
- vr::TrackedPropertyError error = vr::TrackedProp_Success;
- char openvr_string[vr::k_unMaxPropertyStringSize];
- vr_system->GetStringTrackedDeviceProperty(
- device_index, prop, openvr_string, vr::k_unMaxPropertyStringSize, &error);
-
- if (error == vr::TrackedProp_Success)
- out = openvr_string;
-
- return out;
-}
-
-} // namespace device
diff --git a/chromium/device/vr/openvr/openvr_api_wrapper.h b/chromium/device/vr/openvr/openvr_api_wrapper.h
deleted file mode 100644
index 8fb60ec0b39..00000000000
--- a/chromium/device/vr/openvr/openvr_api_wrapper.h
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef DEVICE_VR_OPENVR_OPENVR_API_WRAPPER_H_
-#define DEVICE_VR_OPENVR_OPENVR_API_WRAPPER_H_
-
-#include "base/memory/scoped_refptr.h"
-#include "device/vr/vr_export.h"
-#include "third_party/openvr/src/headers/openvr.h"
-
-namespace base {
-class SingleThreadTaskRunner;
-}
-
-namespace device {
-class VRTestHook;
-class ServiceTestHook;
-
-class OpenVRWrapper {
- public:
- OpenVRWrapper(bool for_rendering);
- ~OpenVRWrapper();
-
- bool IsInitialized() { return initialized_; }
-
- // Gets the OpenVR API objects.
- // Ensures that they are used in a single thread at a time.
- vr::IVRCompositor* GetCompositor();
- vr::IVRSystem* GetSystem();
-
- static void DEVICE_VR_EXPORT SetTestHook(VRTestHook* hook);
-
- private:
- bool Initialize(bool for_rendering);
- void Uninitialize();
-
- vr::IVRSystem* system_ = nullptr;
- vr::IVRCompositor* compositor_ = nullptr;
- scoped_refptr<base::SingleThreadTaskRunner> current_task_runner_;
- bool initialized_ = false;
-
- static ServiceTestHook* service_test_hook_;
- static VRTestHook* test_hook_;
- static bool any_initialized_;
-};
-
-std::string GetOpenVRString(
- vr::IVRSystem* vr_system,
- vr::TrackedDeviceProperty prop,
- uint32_t device_index = vr::k_unTrackedDeviceIndex_Hmd);
-
-} // namespace device
-
-#endif // DEVICE_VR_OPENVR_OPENVR_API_WRAPPER_H_
diff --git a/chromium/device/vr/openvr/openvr_device.cc b/chromium/device/vr/openvr/openvr_device.cc
deleted file mode 100644
index 71fd647f2b1..00000000000
--- a/chromium/device/vr/openvr/openvr_device.cc
+++ /dev/null
@@ -1,310 +0,0 @@
-// Copyright (c) 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "device/vr/openvr/openvr_device.h"
-
-#include <math.h>
-
-#include <utility>
-
-#include "base/bind.h"
-#include "base/bind_helpers.h"
-#include "base/macros.h"
-#include "base/memory/ptr_util.h"
-#include "base/numerics/math_constants.h"
-#include "build/build_config.h"
-#include "device/vr/openvr/openvr_render_loop.h"
-#include "device/vr/openvr/openvr_type_converters.h"
-#include "device/vr/util/stage_utils.h"
-#include "mojo/public/cpp/bindings/pending_remote.h"
-#include "third_party/openvr/src/headers/openvr.h"
-#include "ui/gfx/geometry/angle_conversions.h"
-
-namespace device {
-
-namespace {
-
-constexpr base::TimeDelta kPollingInterval =
- base::TimeDelta::FromSecondsD(0.25);
-
-mojom::VRFieldOfViewPtr OpenVRFovToWebVRFov(vr::IVRSystem* vr_system,
- vr::Hmd_Eye eye) {
- auto out = mojom::VRFieldOfView::New();
- float up_tan, down_tan, left_tan, right_tan;
- vr_system->GetProjectionRaw(eye, &left_tan, &right_tan, &up_tan, &down_tan);
-
- // TODO(billorr): Plumb the expected projection matrix over mojo instead of
- // using angles. Up and down are intentionally swapped to account for
- // differences in expected projection matrix format for GVR and OpenVR.
- out->up_degrees = gfx::RadToDeg(atanf(down_tan));
- out->down_degrees = -gfx::RadToDeg(atanf(up_tan));
- out->left_degrees = -gfx::RadToDeg(atanf(left_tan));
- out->right_degrees = gfx::RadToDeg(atanf(right_tan));
- return out;
-}
-
-gfx::Transform HmdMatrix34ToTransform(const vr::HmdMatrix34_t& mat) {
- // Disable formatting so that the 4x4 matrix is more readable
- // clang-format off
- return gfx::Transform(
- mat.m[0][0], mat.m[0][1], mat.m[0][2], mat.m[0][3],
- mat.m[1][0], mat.m[1][1], mat.m[1][2], mat.m[1][3],
- mat.m[2][0], mat.m[2][1], mat.m[2][2], mat.m[2][3],
- 0.0f, 0.0f, 0.0f, 1.0f);
- // clang-format on
-}
-
-// OpenVR uses A_to_B convention for naming transformation matrices, but we pass
-// matrices through mojo using the B_from_A naming convention since that what
-// blink uses.
-gfx::Transform HeadFromEyeTransform(vr::IVRSystem* vr_system, vr::Hmd_Eye eye) {
- return HmdMatrix34ToTransform(vr_system->GetEyeToHeadTransform(eye));
-}
-
-mojom::VRDisplayInfoPtr CreateVRDisplayInfo(vr::IVRSystem* vr_system,
- device::mojom::XRDeviceId id) {
- mojom::VRDisplayInfoPtr display_info = mojom::VRDisplayInfo::New();
- display_info->id = id;
-
- display_info->left_eye = mojom::VREyeParameters::New();
- display_info->right_eye = mojom::VREyeParameters::New();
- mojom::VREyeParametersPtr& left_eye = display_info->left_eye;
- mojom::VREyeParametersPtr& right_eye = display_info->right_eye;
-
- left_eye->field_of_view = OpenVRFovToWebVRFov(vr_system, vr::Eye_Left);
- right_eye->field_of_view = OpenVRFovToWebVRFov(vr_system, vr::Eye_Right);
-
- left_eye->head_from_eye = HeadFromEyeTransform(vr_system, vr::Eye_Left);
- right_eye->head_from_eye = HeadFromEyeTransform(vr_system, vr::Eye_Right);
-
- uint32_t width, height;
- vr_system->GetRecommendedRenderTargetSize(&width, &height);
- left_eye->render_width = width;
- left_eye->render_height = height;
- right_eye->render_width = left_eye->render_width;
- right_eye->render_height = left_eye->render_height;
-
- display_info->stage_parameters = mojom::VRStageParameters::New();
- vr::HmdMatrix34_t mat =
- vr_system->GetSeatedZeroPoseToStandingAbsoluteTrackingPose();
- gfx::Transform floor_from_mojo = HmdMatrix34ToTransform(mat);
- display_info->stage_parameters->mojo_from_floor = gfx::Transform();
- bool succeeded = floor_from_mojo.GetInverse(
- &display_info->stage_parameters->mojo_from_floor);
- DCHECK(succeeded);
-
- vr::IVRChaperone* chaperone = vr::VRChaperone();
- if (chaperone) {
- float size_x = 0;
- float size_z = 0;
- chaperone->GetPlayAreaSize(&size_x, &size_z);
- display_info->stage_parameters->bounds =
- vr_utils::GetStageBoundsFromSize(size_x, size_z);
- }
- return display_info;
-}
-
-
-} // namespace
-
-OpenVRDevice::OpenVRDevice()
- : VRDeviceBase(device::mojom::XRDeviceId::OPENVR_DEVICE_ID),
- main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()) {
- render_loop_ = std::make_unique<OpenVRRenderLoop>();
-
- OnPollingEvents();
-}
-
-bool OpenVRDevice::IsHwAvailable() {
- return vr::VR_IsHmdPresent();
-}
-
-bool OpenVRDevice::IsApiAvailable() {
- return vr::VR_IsRuntimeInstalled();
-}
-
-mojo::PendingRemote<mojom::XRCompositorHost>
-OpenVRDevice::BindCompositorHost() {
- return compositor_host_receiver_.BindNewPipeAndPassRemote();
-}
-
-OpenVRDevice::~OpenVRDevice() {
- Shutdown();
-}
-
-void OpenVRDevice::Shutdown() {
- // Wait for the render loop to stop before completing destruction. This will
- // ensure that the IVRSystem doesn't get shutdown until the render loop is no
- // longer referencing it.
- if (render_loop_ && render_loop_->IsRunning())
- render_loop_->Stop();
-}
-
-void OpenVRDevice::RequestSession(
- mojom::XRRuntimeSessionOptionsPtr options,
- mojom::XRRuntime::RequestSessionCallback callback) {
- if (!EnsureValidDisplayInfo()) {
- std::move(callback).Run(nullptr, mojo::NullRemote());
- return;
- }
-
- DCHECK_EQ(options->mode, mojom::XRSessionMode::kImmersiveVr);
-
- if (!render_loop_->IsRunning()) {
- render_loop_->Start();
-
- if (!render_loop_->IsRunning()) {
- std::move(callback).Run(nullptr, mojo::NullRemote());
- return;
- }
-
- if (overlay_receiver_) {
- render_loop_->task_runner()->PostTask(
- FROM_HERE, base::BindOnce(&XRCompositorCommon::RequestOverlay,
- base::Unretained(render_loop_.get()),
- std::move(overlay_receiver_)));
- }
- }
-
- // We are done using OpenVR until the presentation session ends.
- openvr_ = nullptr;
-
- auto my_callback =
- base::BindOnce(&OpenVRDevice::OnRequestSessionResult,
- weak_ptr_factory_.GetWeakPtr(), std::move(callback));
-
- auto on_presentation_ended = base::BindOnce(
- &OpenVRDevice::OnPresentationEnded, weak_ptr_factory_.GetWeakPtr());
-
- render_loop_->task_runner()->PostTask(
- FROM_HERE,
- base::BindOnce(&XRCompositorCommon::RequestSession,
- base::Unretained(render_loop_.get()),
- std::move(on_presentation_ended),
- base::DoNothing::Repeatedly<mojom::XRVisibilityState>(),
- std::move(options), std::move(my_callback)));
- outstanding_session_requests_count_++;
-}
-
-bool OpenVRDevice::EnsureValidDisplayInfo() {
- // Ensure we have had a valid display_info set at least once.
- if (!have_real_display_info_) {
- DCHECK(!openvr_);
- // Initialize OpenVR.
- openvr_ = std::make_unique<OpenVRWrapper>(false /* presenting */);
- if (!openvr_->IsInitialized()) {
- openvr_ = nullptr;
- return false;
- }
-
- SetVRDisplayInfo(CreateVRDisplayInfo(openvr_->GetSystem(), GetId()));
- have_real_display_info_ = true;
- }
- return have_real_display_info_;
-}
-
-void OpenVRDevice::OnPresentationEnded() {
- if (!openvr_ && outstanding_session_requests_count_ == 0) {
- openvr_ = std::make_unique<OpenVRWrapper>(false /* presenting */);
- if (!openvr_->IsInitialized()) {
- openvr_ = nullptr;
- return;
- }
- }
-}
-
-void OpenVRDevice::OnRequestSessionResult(
- mojom::XRRuntime::RequestSessionCallback callback,
- bool result,
- mojom::XRSessionPtr session) {
- outstanding_session_requests_count_--;
- if (!result) {
- OnPresentationEnded();
- std::move(callback).Run(nullptr, mojo::NullRemote());
- return;
- }
-
- OnStartPresenting();
-
- session->display_info = display_info_.Clone();
-
- std::move(callback).Run(
- std::move(session),
- exclusive_controller_receiver_.BindNewPipeAndPassRemote());
-
- // Use of Unretained is safe because the callback will only occur if the
- // binding is not destroyed.
- exclusive_controller_receiver_.set_disconnect_handler(
- base::BindOnce(&OpenVRDevice::OnPresentingControllerMojoConnectionError,
- base::Unretained(this)));
-}
-
-bool OpenVRDevice::IsAvailable() {
- return vr::VR_IsRuntimeInstalled() && vr::VR_IsHmdPresent();
-}
-
-void OpenVRDevice::CreateImmersiveOverlay(
- mojo::PendingReceiver<mojom::ImmersiveOverlay> overlay_receiver) {
- if (render_loop_->IsRunning()) {
- render_loop_->task_runner()->PostTask(
- FROM_HERE, base::BindOnce(&XRCompositorCommon::RequestOverlay,
- base::Unretained(render_loop_.get()),
- std::move(overlay_receiver)));
- } else {
- overlay_receiver_ = std::move(overlay_receiver);
- }
-}
-
-// XRSessionController
-void OpenVRDevice::SetFrameDataRestricted(bool restricted) {
- // Presentation sessions can not currently be restricted.
- DCHECK(false);
-}
-
-void OpenVRDevice::OnPresentingControllerMojoConnectionError() {
- render_loop_->task_runner()->PostTask(
- FROM_HERE, base::BindOnce(&XRCompositorCommon::ExitPresent,
- base::Unretained(render_loop_.get())));
- OnExitPresent();
- exclusive_controller_receiver_.reset();
-}
-
-// Only deal with events that will cause displayInfo changes for now.
-void OpenVRDevice::OnPollingEvents() {
- main_thread_task_runner_->PostDelayedTask(
- FROM_HERE,
- base::BindOnce(&OpenVRDevice::OnPollingEvents,
- weak_ptr_factory_.GetWeakPtr()),
- kPollingInterval);
-
- if (!openvr_)
- return;
-
- vr::VREvent_t event;
- bool is_changed = false;
- while (openvr_->GetSystem()->PollNextEvent(&event, sizeof(event))) {
- if (event.trackedDeviceIndex != vr::k_unTrackedDeviceIndex_Hmd &&
- event.trackedDeviceIndex != vr::k_unTrackedDeviceIndexInvalid) {
- continue;
- }
-
- switch (event.eventType) {
- case vr::VREvent_TrackedDeviceUpdated:
- case vr::VREvent_IpdChanged:
- case vr::VREvent_ChaperoneDataHasChanged:
- case vr::VREvent_ChaperoneSettingsHaveChanged:
- case vr::VREvent_ChaperoneUniverseHasChanged:
- is_changed = true;
- break;
-
- default:
- break;
- }
- }
-
- if (is_changed)
- SetVRDisplayInfo(CreateVRDisplayInfo(openvr_->GetSystem(), GetId()));
-}
-
-} // namespace device
diff --git a/chromium/device/vr/openvr/openvr_device.h b/chromium/device/vr/openvr/openvr_device.h
deleted file mode 100644
index 919e0dc4ff5..00000000000
--- a/chromium/device/vr/openvr/openvr_device.h
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef DEVICE_VR_OPENVR_OPENVR_DEVICE_H_
-#define DEVICE_VR_OPENVR_OPENVR_DEVICE_H_
-
-#include <memory>
-
-#include "base/macros.h"
-#include "base/single_thread_task_runner.h"
-#include "device/vr/openvr/openvr_api_wrapper.h"
-#include "device/vr/public/mojom/vr_service.mojom.h"
-#include "device/vr/vr_device_base.h"
-#include "mojo/public/cpp/bindings/pending_receiver.h"
-#include "mojo/public/cpp/bindings/pending_remote.h"
-#include "mojo/public/cpp/bindings/receiver.h"
-
-namespace device {
-
-class XRCompositorCommon;
-
-class DEVICE_VR_EXPORT OpenVRDevice
- : public VRDeviceBase,
- public mojom::XRSessionController,
- public mojom::XRCompositorHost {
- public:
- OpenVRDevice();
- ~OpenVRDevice() override;
-
- static bool IsHwAvailable();
- static bool IsApiAvailable();
-
- void Shutdown();
-
- // VRDeviceBase
- void RequestSession(
- mojom::XRRuntimeSessionOptionsPtr options,
- mojom::XRRuntime::RequestSessionCallback callback) override;
-
- void OnPollingEvents();
-
- void OnRequestSessionResult(mojom::XRRuntime::RequestSessionCallback callback,
- bool result,
- mojom::XRSessionPtr session);
-
- bool IsAvailable();
-
- mojo::PendingRemote<mojom::XRCompositorHost> BindCompositorHost();
-
- private:
- // XRSessionController
- void SetFrameDataRestricted(bool restricted) override;
-
- // XRCompositorHost
- void CreateImmersiveOverlay(
- mojo::PendingReceiver<mojom::ImmersiveOverlay> overlay_receiver) override;
-
- void OnPresentingControllerMojoConnectionError();
- void OnPresentationEnded();
- bool EnsureValidDisplayInfo();
-
- int outstanding_session_requests_count_ = 0;
- bool have_real_display_info_ = false;
- std::unique_ptr<XRCompositorCommon> render_loop_;
- std::unique_ptr<OpenVRWrapper> openvr_;
- scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_;
-
- mojo::Receiver<mojom::XRSessionController> exclusive_controller_receiver_{
- this};
-
- mojo::Receiver<mojom::XRCompositorHost> compositor_host_receiver_{this};
- mojo::PendingReceiver<mojom::ImmersiveOverlay> overlay_receiver_;
-
- base::WeakPtrFactory<OpenVRDevice> weak_ptr_factory_{this};
-
- DISALLOW_COPY_AND_ASSIGN(OpenVRDevice);
-};
-
-} // namespace device
-
-#endif // DEVICE_VR_OPENVR_OPENVR_DEVICE_H_
diff --git a/chromium/device/vr/openvr/openvr_gamepad_helper.cc b/chromium/device/vr/openvr/openvr_gamepad_helper.cc
deleted file mode 100644
index f1875bedff4..00000000000
--- a/chromium/device/vr/openvr/openvr_gamepad_helper.cc
+++ /dev/null
@@ -1,333 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "device/vr/openvr/openvr_gamepad_helper.h"
-#include "device/vr/openvr/openvr_api_wrapper.h"
-
-#include <memory>
-#include <unordered_set>
-
-#include "base/compiler_specific.h"
-#include "base/strings/string_split.h"
-#include "base/strings/string_util.h"
-#include "base/strings/utf_string_conversions.h"
-#include "device/gamepad/public/cpp/gamepads.h"
-#include "device/vr/util/gamepad_builder.h"
-#include "device/vr/util/xr_standard_gamepad_builder.h"
-#include "device/vr/vr_device.h"
-#include "third_party/openvr/src/headers/openvr.h"
-#include "ui/gfx/transform.h"
-#include "ui/gfx/transform_util.h"
-
-namespace device {
-
-namespace {
-
-constexpr double kJoystickDeadzone = 0.16;
-
-bool TryGetGamepadButton(const vr::VRControllerState_t& controller_state,
- uint64_t supported_buttons,
- vr::EVRButtonId button_id,
- GamepadButton* button) {
- uint64_t button_mask = vr::ButtonMaskFromId(button_id);
- if ((supported_buttons & button_mask) != 0) {
- bool button_pressed = (controller_state.ulButtonPressed & button_mask) != 0;
- bool button_touched = (controller_state.ulButtonTouched & button_mask) != 0;
- button->touched = button_touched || button_pressed;
- button->pressed = button_pressed;
- button->value = button_pressed ? 1.0 : 0.0;
- return true;
- }
-
- return false;
-}
-
-vr::EVRButtonId GetAxisId(uint32_t axis_offset) {
- return static_cast<vr::EVRButtonId>(vr::k_EButton_Axis0 + axis_offset);
-}
-
-std::map<vr::EVRButtonId, GamepadBuilder::ButtonData> GetAxesButtons(
- vr::IVRSystem* vr_system,
- const vr::VRControllerState_t& controller_state,
- uint64_t supported_buttons,
- uint32_t controller_id) {
- std::map<vr::EVRButtonId, GamepadBuilder::ButtonData> button_data_map;
-
- for (uint32_t j = 0; j < vr::k_unControllerStateAxisCount; ++j) {
- int32_t axis_type = vr_system->GetInt32TrackedDeviceProperty(
- controller_id,
- static_cast<vr::TrackedDeviceProperty>(vr::Prop_Axis0Type_Int32 + j));
-
- GamepadBuilder::ButtonData button_data;
-
- // Invert the y axis because -1 is up in the Gamepad API but down in OpenVR.
- double x_axis = controller_state.rAxis[j].x;
- double y_axis = -controller_state.rAxis[j].y;
-
- if (axis_type == vr::k_eControllerAxis_Joystick) {
- button_data.type = GamepadBuilder::ButtonData::Type::kThumbstick;
-
- // We only want to apply the deadzone to joysticks, since various
- // runtimes may not have already done that, but touchpads should
- // be fine.
- x_axis = std::fabs(x_axis) < kJoystickDeadzone ? 0 : x_axis;
- y_axis = std::fabs(y_axis) < kJoystickDeadzone ? 0 : y_axis;
- } else if (axis_type == vr::k_eControllerAxis_TrackPad) {
- button_data.type = GamepadBuilder::ButtonData::Type::kTouchpad;
- }
-
- switch (axis_type) {
- case vr::k_eControllerAxis_Joystick:
- case vr::k_eControllerAxis_TrackPad: {
- button_data.x_axis = x_axis;
- button_data.y_axis = y_axis;
- vr::EVRButtonId button_id = GetAxisId(j);
-
- // Even if the button associated with the axis isn't supported, if we
- // have valid axis data, we should still send that up. Since the spec
- // expects buttons with axes, then we will add a dummy button to match
- // the axes.
- GamepadButton button;
- if (TryGetGamepadButton(controller_state, supported_buttons, button_id,
- &button)) {
- button_data.touched = button.touched;
- button_data.pressed = button.pressed;
- button_data.value = button.value;
- } else {
- button_data.pressed = false;
- button_data.value = 0.0;
- button_data.touched =
- (std::fabs(x_axis) > 0 || std::fabs(y_axis) > 0);
- }
-
- button_data_map[button_id] = button_data;
- } break;
- case vr::k_eControllerAxis_Trigger: {
- GamepadButton button;
- GamepadBuilder::ButtonData button_data;
- vr::EVRButtonId button_id = GetAxisId(j);
- if (TryGetGamepadButton(controller_state, supported_buttons, button_id,
- &button)) {
- button_data.touched = button.touched;
- button_data.pressed = button.pressed;
- button_data.value = x_axis;
- button_data_map[button_id] = button_data;
- }
- } break;
- }
- }
-
- return button_data_map;
-}
-
-constexpr std::array<vr::EVRButtonId, 5> kWebXRButtonOrder = {
- vr::k_EButton_A, vr::k_EButton_DPad_Left, vr::k_EButton_DPad_Up,
- vr::k_EButton_DPad_Right, vr::k_EButton_DPad_Down,
-};
-
-// To make sure this string fits the requirements of the WebXR spec, separate
-// words/tokens are separated by "-" instead of whitespace and convert it to
-// lowercase.
-std::string FixupProfileString(const std::string& name) {
- std::vector<std::string> tokens =
- base::SplitString(name, base::kWhitespaceASCII, base::KEEP_WHITESPACE,
- base::SPLIT_WANT_NONEMPTY);
- std::string result = base::JoinString(tokens, "-");
- return base::ToLowerASCII(result);
-}
-
-} // namespace
-
-// Helper classes and WebXR Getters
-class OpenVRGamepadBuilder : public XRStandardGamepadBuilder {
- public:
- enum class AxesRequirement {
- kOptional = 0,
- kRequireBoth = 1,
- };
-
- OpenVRGamepadBuilder(vr::IVRSystem* vr_system,
- uint32_t controller_id,
- vr::VRControllerState_t controller_state,
- mojom::XRHandedness handedness)
- : XRStandardGamepadBuilder(handedness),
- controller_state_(controller_state) {
- supported_buttons_ = vr_system->GetUint64TrackedDeviceProperty(
- controller_id, vr::Prop_SupportedButtons_Uint64);
-
- axes_data_ = GetAxesButtons(vr_system, controller_state_,
- supported_buttons_, controller_id);
-
- base::Optional<GamepadBuilder::ButtonData> primary_button =
- TryGetAxesOrTriggerButton(vr::k_EButton_SteamVR_Trigger);
-
- if (!primary_button) {
- return;
- }
-
- SetPrimaryButton(primary_button.value());
-
- base::Optional<GamepadButton> secondary_button =
- TryGetButton(vr::k_EButton_Grip);
- if (secondary_button) {
- SetSecondaryButton(secondary_button.value());
- }
-
- base::Optional<GamepadBuilder::ButtonData> touchpad_data =
- TryGetNextUnusedButtonOfType(
- GamepadBuilder::ButtonData::Type::kTouchpad);
- if (touchpad_data) {
- SetTouchpadData(touchpad_data.value());
- }
-
- base::Optional<GamepadBuilder::ButtonData> thumbstick_data =
- TryGetNextUnusedButtonOfType(
- GamepadBuilder::ButtonData::Type::kThumbstick);
- if (thumbstick_data) {
- SetThumbstickData(thumbstick_data.value());
- }
-
- // Now that all of the xr-standard reserved buttons have been filled in, we
- // add the rest of the buttons in order of decreasing importance.
- // First add regular buttons.
- for (const auto& id : kWebXRButtonOrder) {
- base::Optional<GamepadButton> button = TryGetButton(id);
- if (button) {
- AddOptionalButtonData(button.value());
- }
- }
-
- // Finally, add any remaining axis buttons (triggers/josysticks/touchpads)
- AddRemainingTriggersAndAxes();
-
- // Find out the model and manufacturer names in case the caller wants this
- // information for the input profiles array.
- std::string model =
- GetOpenVRString(vr_system, vr::Prop_ModelNumber_String, controller_id);
- std::string manufacturer = GetOpenVRString(
- vr_system, vr::Prop_ManufacturerName_String, controller_id);
-
- UpdateProfiles(manufacturer, model);
- }
-
- ~OpenVRGamepadBuilder() override = default;
-
- std::vector<std::string> GetProfiles() const { return profiles_; }
-
- private:
- void UpdateProfiles(const std::string& manufacturer,
- const std::string& model) {
- // Per the WebXR spec, the first entry in the profiles array should be the
- // most specific one.
- std::string name =
- FixupProfileString(manufacturer) + "-" + FixupProfileString(model);
- profiles_.push_back(name);
-
- // Also record information about what this controller actually does in a
- // more general sense. The controller is guaranteed to at least have a
- // trigger if we get here.
- std::string capabilities = "generic-trigger";
- if (HasSecondaryButton()) {
- capabilities += "-squeeze";
- }
- if (HasTouchpad()) {
- capabilities += "-touchpad";
- }
- if (HasThumbstick()) {
- capabilities += "-thumbstick";
- }
- profiles_.push_back(capabilities);
- }
-
- base::Optional<GamepadBuilder::ButtonData> TryGetAxesOrTriggerButton(
- vr::EVRButtonId button_id,
- AxesRequirement requirement = AxesRequirement::kOptional) {
- if (!IsInAxesData(button_id))
- return base::nullopt;
-
- bool require_axes = (requirement == AxesRequirement::kRequireBoth);
- if (require_axes &&
- axes_data_[button_id].type == GamepadBuilder::ButtonData::Type::kButton)
- return base::nullopt;
-
- used_axes_.insert(button_id);
- return axes_data_[button_id];
- }
-
- base::Optional<GamepadBuilder::ButtonData> TryGetNextUnusedButtonOfType(
- GamepadBuilder::ButtonData::Type type) {
- for (const auto& axes_data_pair : axes_data_) {
- vr::EVRButtonId button_id = axes_data_pair.first;
- if (IsUsed(button_id))
- continue;
-
- if (axes_data_pair.second.type != type)
- continue;
-
- return TryGetAxesOrTriggerButton(button_id,
- AxesRequirement::kRequireBoth);
- }
-
- return base::nullopt;
- }
-
- base::Optional<GamepadButton> TryGetButton(vr::EVRButtonId button_id) {
- GamepadButton button;
- if (TryGetGamepadButton(controller_state_, supported_buttons_, button_id,
- &button)) {
- return button;
- }
-
- return base::nullopt;
- }
-
- // This will add any remaining unused values from axes_data to the gamepad.
- // Returns a bool indicating whether any additional axes were added.
- void AddRemainingTriggersAndAxes() {
- for (const auto& axes_data_pair : axes_data_) {
- if (!IsUsed(axes_data_pair.first)) {
- AddOptionalButtonData(axes_data_pair.second);
- }
- }
- }
-
- bool IsUsed(vr::EVRButtonId button_id) {
- auto it = used_axes_.find(button_id);
- return it != used_axes_.end();
- }
-
- bool IsInAxesData(vr::EVRButtonId button_id) {
- auto it = axes_data_.find(button_id);
- return it != axes_data_.end();
- }
-
- const vr::VRControllerState_t controller_state_;
- uint64_t supported_buttons_;
- std::map<vr::EVRButtonId, GamepadBuilder::ButtonData> axes_data_;
- std::unordered_set<vr::EVRButtonId> used_axes_;
- std::vector<std::string> profiles_;
-
- DISALLOW_COPY_AND_ASSIGN(OpenVRGamepadBuilder);
-};
-
-OpenVRInputSourceData::OpenVRInputSourceData() = default;
-OpenVRInputSourceData::~OpenVRInputSourceData() = default;
-OpenVRInputSourceData::OpenVRInputSourceData(
- const OpenVRInputSourceData& other) = default;
-
-OpenVRInputSourceData OpenVRGamepadHelper::GetXRInputSourceData(
- vr::IVRSystem* vr_system,
- uint32_t controller_id,
- vr::VRControllerState_t controller_state,
- mojom::XRHandedness handedness) {
- OpenVRGamepadBuilder builder(vr_system, controller_id, controller_state,
- handedness);
-
- OpenVRInputSourceData data;
- data.gamepad = builder.GetGamepad();
- data.profiles = builder.GetProfiles();
- return data;
-}
-
-} // namespace device
diff --git a/chromium/device/vr/openvr/openvr_gamepad_helper.h b/chromium/device/vr/openvr/openvr_gamepad_helper.h
deleted file mode 100644
index 591d8581d66..00000000000
--- a/chromium/device/vr/openvr/openvr_gamepad_helper.h
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef DEVICE_VR_OPENVR_OPENVR_GAMEPAD_HELPER_H_
-#define DEVICE_VR_OPENVR_OPENVR_GAMEPAD_HELPER_H_
-
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "base/optional.h"
-#include "device/gamepad/public/cpp/gamepads.h"
-#include "device/vr/public/mojom/vr_service.mojom.h"
-#include "third_party/openvr/src/headers/openvr.h"
-
-namespace device {
-
-struct OpenVRInputSourceData {
- OpenVRInputSourceData();
- ~OpenVRInputSourceData();
- OpenVRInputSourceData(const OpenVRInputSourceData& other);
- base::Optional<Gamepad> gamepad;
- std::vector<std::string> profiles;
-};
-
-class OpenVRGamepadHelper {
- public:
- static OpenVRInputSourceData GetXRInputSourceData(
- vr::IVRSystem* system,
- uint32_t controller_id,
- vr::VRControllerState_t controller_state,
- mojom::XRHandedness handedness);
-};
-
-} // namespace device
-#endif // DEVICE_VR_OPENVR_OPENVR_GAMEPAD_HELPER_H_
diff --git a/chromium/device/vr/openvr/openvr_render_loop.cc b/chromium/device/vr/openvr/openvr_render_loop.cc
deleted file mode 100644
index 97abc700f6b..00000000000
--- a/chromium/device/vr/openvr/openvr_render_loop.cc
+++ /dev/null
@@ -1,324 +0,0 @@
-// Copyright (c) 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "device/vr/openvr/openvr_render_loop.h"
-
-#include "base/trace_event/trace_event.h"
-#include "device/vr/openvr/openvr_api_wrapper.h"
-#include "device/vr/openvr/openvr_gamepad_helper.h"
-#include "device/vr/openvr/openvr_type_converters.h"
-#include "ui/gfx/geometry/angle_conversions.h"
-#include "ui/gfx/transform.h"
-
-#if defined(OS_WIN)
-#include "device/vr/windows/d3d11_texture_helper.h"
-#endif
-
-namespace device {
-
-namespace {
-
-// OpenVR reports the controllers pose of the controller's tip, while WebXR
-// needs to report the pose of the controller's grip (centered on the user's
-// palm.) This experimentally determined value is how far back along the Z axis
-// in meters OpenVR's pose needs to be translated to align with WebXR's
-// coordinate system.
-const float kGripOffsetZMeters = 0.08f;
-
-// WebXR reports a pointer pose separate from the grip pose, which represents a
-// pointer ray emerging from the tip of the controller. OpenVR does not report
-// anything like that, and most pointers are assumed to come straight from the
-// controller's tip. For consistency with other WebXR backends we'll synthesize
-// a pointer ray that's angled down slightly from the controller's handle,
-// defined by this angle. Experimentally determined, should roughly point in the
-// same direction as a user's outstretched index finger while holding a
-// controller.
-const float kPointerErgoAngleDegrees = -40.0f;
-
-gfx::Transform HmdMatrix34ToTransform(const vr::HmdMatrix34_t& mat) {
- return gfx::Transform(mat.m[0][0], mat.m[0][1], mat.m[0][2], mat.m[0][3],
- mat.m[1][0], mat.m[1][1], mat.m[1][2], mat.m[1][3],
- mat.m[2][0], mat.m[2][1], mat.m[2][2], mat.m[2][3], 0,
- 0, 0, 1);
-}
-
-device::mojom::XRHandedness ConvertToMojoHandedness(
- vr::ETrackedControllerRole controller_role) {
- switch (controller_role) {
- case vr::TrackedControllerRole_LeftHand:
- return device::mojom::XRHandedness::LEFT;
- case vr::TrackedControllerRole_RightHand:
- return device::mojom::XRHandedness::RIGHT;
- case vr::TrackedControllerRole_Invalid:
- case vr::TrackedControllerRole_OptOut:
- case vr::TrackedControllerRole_Treadmill:
- case vr::TrackedControllerRole_Max:
- return device::mojom::XRHandedness::NONE;
- }
-
- NOTREACHED();
-}
-
-} // namespace
-
-void OpenVRRenderLoop::InputActiveState::MarkAsInactive() {
- active = false;
- primary_input_pressed = false;
- device_class = vr::TrackedDeviceClass_Invalid;
- controller_role = vr::TrackedControllerRole_Invalid;
-}
-
-OpenVRRenderLoop::OpenVRRenderLoop() : XRCompositorCommon() {}
-
-OpenVRRenderLoop::~OpenVRRenderLoop() {
- Stop();
-}
-
-bool OpenVRRenderLoop::PreComposite() {
- texture_helper_.AllocateBackBuffer();
- return true;
-}
-
-bool OpenVRRenderLoop::SubmitCompositedFrame() {
- DCHECK(openvr_);
- vr::IVRCompositor* vr_compositor = openvr_->GetCompositor();
- DCHECK(vr_compositor);
- if (!vr_compositor)
- return false;
-
- vr::Texture_t texture;
- texture.handle = texture_helper_.GetBackbuffer().Get();
- texture.eType = vr::TextureType_DirectX;
- texture.eColorSpace = vr::ColorSpace_Auto;
-
- gfx::RectF left_bounds = texture_helper_.BackBufferLeft();
- gfx::RectF right_bounds = texture_helper_.BackBufferRight();
-
- vr::VRTextureBounds_t bounds[2];
- bounds[0] = {left_bounds.x(), left_bounds.y(),
- left_bounds.width() + left_bounds.x(),
- left_bounds.height() + left_bounds.y()};
- bounds[1] = {right_bounds.x(), right_bounds.y(),
- right_bounds.width() + right_bounds.x(),
- right_bounds.height() + right_bounds.y()};
-
- vr::EVRCompositorError error =
- vr_compositor->Submit(vr::EVREye::Eye_Left, &texture, &bounds[0]);
- if (error != vr::VRCompositorError_None) {
- return false;
- }
- error = vr_compositor->Submit(vr::EVREye::Eye_Right, &texture, &bounds[1]);
- if (error != vr::VRCompositorError_None) {
- return false;
- }
- vr_compositor->PostPresentHandoff();
- return true;
-}
-
-bool OpenVRRenderLoop::StartRuntime() {
- if (!openvr_) {
- openvr_ = std::make_unique<OpenVRWrapper>(true);
- if (!openvr_->IsInitialized()) {
- openvr_ = nullptr;
- return false;
- }
-
- openvr_->GetCompositor()->SuspendRendering(true);
- openvr_->GetCompositor()->SetTrackingSpace(
- vr::ETrackingUniverseOrigin::TrackingUniverseSeated);
- }
-
-#if defined(OS_WIN)
- int32_t adapter_index;
- openvr_->GetSystem()->GetDXGIOutputInfo(&adapter_index);
- if (!texture_helper_.SetAdapterIndex(adapter_index) ||
- !texture_helper_.EnsureInitialized()) {
- openvr_ = nullptr;
- return false;
- }
-#endif
-
- uint32_t width, height;
- openvr_->GetSystem()->GetRecommendedRenderTargetSize(&width, &height);
- texture_helper_.SetDefaultSize(gfx::Size(width, height));
-
- return true;
-}
-
-void OpenVRRenderLoop::StopRuntime() {
- if (openvr_)
- openvr_->GetCompositor()->SuspendRendering(true);
- openvr_ = nullptr;
-}
-
-void OpenVRRenderLoop::OnSessionStart() {
- // Reset the active states for all the controllers.
- for (uint32_t i = 0; i < vr::k_unMaxTrackedDeviceCount; ++i) {
- InputActiveState& input_active_state = input_active_states_[i];
- input_active_state.active = false;
- input_active_state.primary_input_pressed = false;
- input_active_state.device_class = vr::TrackedDeviceClass_Invalid;
- input_active_state.controller_role = vr::TrackedControllerRole_Invalid;
- }
-
- openvr_->GetCompositor()->SuspendRendering(false);
-
- // Measure the VrViewerType we are presenting with.
- std::string model =
- GetOpenVRString(openvr_->GetSystem(), vr::Prop_ModelNumber_String);
- VrViewerType type = VrViewerType::OPENVR_UNKNOWN;
- if (model == "Oculus Rift CV1")
- type = VrViewerType::OPENVR_RIFT_CV1;
- else if (model == "Vive MV")
- type = VrViewerType::OPENVR_VIVE;
-
- LogViewerType(type);
-}
-
-mojom::XRFrameDataPtr OpenVRRenderLoop::GetNextFrameData() {
- mojom::XRFrameDataPtr frame_data = mojom::XRFrameData::New();
- frame_data->frame_id = next_frame_id_;
-
- if (openvr_) {
- vr::TrackedDevicePose_t rendering_poses[vr::k_unMaxTrackedDeviceCount];
-
- TRACE_EVENT0("gpu", "WaitGetPoses");
- openvr_->GetCompositor()->WaitGetPoses(
- rendering_poses, vr::k_unMaxTrackedDeviceCount, nullptr, 0);
-
- frame_data->pose = mojo::ConvertTo<mojom::VRPosePtr>(
- rendering_poses[vr::k_unTrackedDeviceIndex_Hmd]);
-
- // Update WebXR input sources.
- frame_data->input_state =
- GetInputState(rendering_poses, vr::k_unMaxTrackedDeviceCount);
-
- vr::Compositor_FrameTiming timing;
- timing.m_nSize = sizeof(vr::Compositor_FrameTiming);
- bool valid_time = openvr_->GetCompositor()->GetFrameTiming(&timing);
- if (valid_time) {
- frame_data->time_delta =
- base::TimeDelta::FromSecondsD(timing.m_flSystemTimeInSeconds);
- }
- }
-
- return frame_data;
-}
-
-std::vector<mojom::XRInputSourceStatePtr> OpenVRRenderLoop::GetInputState(
- vr::TrackedDevicePose_t* poses,
- uint32_t count) {
- std::vector<mojom::XRInputSourceStatePtr> input_states;
-
- if (!openvr_)
- return input_states;
-
- // Loop through every device pose and determine which are controllers
- for (uint32_t i = vr::k_unTrackedDeviceIndex_Hmd + 1; i < count; ++i) {
- const vr::TrackedDevicePose_t& pose = poses[i];
- InputActiveState& input_active_state = input_active_states_[i];
-
- if (!pose.bDeviceIsConnected) {
- // If this was an active controller on the last frame report it as
- // disconnected.
- if (input_active_state.active)
- input_active_state.MarkAsInactive();
- continue;
- }
-
- // Is this a newly connected controller?
- bool newly_active = false;
- if (!input_active_state.active) {
- input_active_state.active = true;
- input_active_state.device_class =
- openvr_->GetSystem()->GetTrackedDeviceClass(i);
- newly_active = true;
- }
-
- // Skip over any tracked devices that aren't controllers.
- if (input_active_state.device_class != vr::TrackedDeviceClass_Controller) {
- continue;
- }
-
- device::mojom::XRInputSourceStatePtr state =
- device::mojom::XRInputSourceState::New();
-
- vr::VRControllerState_t controller_state;
- bool have_state = openvr_->GetSystem()->GetControllerState(
- i, &controller_state, sizeof(vr::VRControllerState_t));
- if (!have_state) {
- input_active_state.MarkAsInactive();
- continue;
- }
-
- bool pressed = controller_state.ulButtonPressed &
- vr::ButtonMaskFromId(vr::k_EButton_SteamVR_Trigger);
-
- state->source_id = i;
- state->primary_input_pressed = pressed;
- state->primary_input_clicked =
- (!pressed && input_active_state.primary_input_pressed);
-
- input_active_state.primary_input_pressed = pressed;
-
- if (pose.bPoseIsValid) {
- state->mojo_from_input =
- HmdMatrix34ToTransform(pose.mDeviceToAbsoluteTracking);
- // Scoot the grip matrix back a bit so that it actually lines up with the
- // user's palm.
- state->mojo_from_input->Translate3d(0, 0, kGripOffsetZMeters);
- }
-
- // Poll controller roll per-frame, since OpenVR controllers can swap hands.
- vr::ETrackedControllerRole controller_role =
- openvr_->GetSystem()->GetControllerRoleForTrackedDeviceIndex(i);
-
- device::mojom::XRHandedness handedness =
- ConvertToMojoHandedness(controller_role);
-
- OpenVRInputSourceData input_source_data =
- OpenVRGamepadHelper::GetXRInputSourceData(openvr_->GetSystem(), i,
- controller_state, handedness);
- state->gamepad = input_source_data.gamepad;
-
- // OpenVR controller are fully 6DoF.
- state->emulated_position = false;
-
- // Re-send the controller's description if it's newly active or if the
- // handedness or profile strings have changed.
- if (newly_active ||
- (controller_role != input_active_state.controller_role) ||
- (input_source_data.profiles != input_active_state.profiles)) {
- device::mojom::XRInputSourceDescriptionPtr desc =
- device::mojom::XRInputSourceDescription::New();
-
- // It's a handheld pointing device.
- desc->target_ray_mode = device::mojom::XRTargetRayMode::POINTING;
-
- desc->handedness = handedness;
- input_active_state.controller_role = controller_role;
-
- // Tweak the pointer transform so that it's angled down from the
- // grip. This should be a bit more ergonomic.
- desc->input_from_pointer = gfx::Transform();
- desc->input_from_pointer->RotateAboutXAxis(kPointerErgoAngleDegrees);
-
- desc->profiles = input_source_data.profiles;
-
- state->description = std::move(desc);
-
- // Keep track of the current profiles so we know if it changes next frame.
- input_active_state.profiles = input_source_data.profiles;
- }
-
- input_states.push_back(std::move(state));
- }
-
- return input_states;
-}
-
-OpenVRRenderLoop::InputActiveState::InputActiveState() = default;
-OpenVRRenderLoop::InputActiveState::~InputActiveState() = default;
-
-} // namespace device
diff --git a/chromium/device/vr/openvr/openvr_render_loop.h b/chromium/device/vr/openvr/openvr_render_loop.h
deleted file mode 100644
index 327532db79c..00000000000
--- a/chromium/device/vr/openvr/openvr_render_loop.h
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright (c) 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef DEVICE_VR_OPENVR_RENDER_LOOP_H
-#define DEVICE_VR_OPENVR_RENDER_LOOP_H
-
-#include <string>
-#include <vector>
-
-#include "base/memory/scoped_refptr.h"
-#include "base/threading/thread.h"
-#include "base/time/time.h"
-#include "build/build_config.h"
-#include "device/vr/public/mojom/vr_service.mojom.h"
-#include "device/vr/vr_device.h"
-#include "device/vr/windows/compositor_base.h"
-#include "mojo/public/cpp/system/platform_handle.h"
-#include "third_party/openvr/src/headers/openvr.h"
-#include "ui/gfx/geometry/rect_f.h"
-
-#if defined(OS_WIN)
-#include "device/vr/windows/d3d11_texture_helper.h"
-#endif
-
-namespace device {
-
-class OpenVRWrapper;
-
-class OpenVRRenderLoop : public XRCompositorCommon {
- public:
- OpenVRRenderLoop();
- ~OpenVRRenderLoop() override;
-
- private:
- // XRDeviceAbstraction:
- mojom::XRFrameDataPtr GetNextFrameData() override;
- bool StartRuntime() override;
- void StopRuntime() override;
- void OnSessionStart() override;
- bool PreComposite() override;
- bool SubmitCompositedFrame() override;
-
- // Helpers to implement XRDeviceAbstraction.
- std::vector<mojom::XRInputSourceStatePtr> GetInputState(
- vr::TrackedDevicePose_t* poses,
- uint32_t count);
-
- struct InputActiveState {
- bool active;
- bool primary_input_pressed;
- vr::ETrackedDeviceClass device_class;
- vr::ETrackedControllerRole controller_role;
-
- std::vector<std::string> profiles;
-
- InputActiveState();
- ~InputActiveState();
- void MarkAsInactive();
-
- DISALLOW_COPY_AND_ASSIGN(InputActiveState);
- };
-
- InputActiveState input_active_states_[vr::k_unMaxTrackedDeviceCount];
- std::unique_ptr<OpenVRWrapper> openvr_;
-
- DISALLOW_COPY_AND_ASSIGN(OpenVRRenderLoop);
-};
-
-} // namespace device
-
-#endif // DEVICE_VR_OPENVR_RENDER_LOOP_H
diff --git a/chromium/device/vr/openvr/openvr_type_converters.cc b/chromium/device/vr/openvr/openvr_type_converters.cc
deleted file mode 100644
index ce8fb793aa8..00000000000
--- a/chromium/device/vr/openvr/openvr_type_converters.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (c) 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "device/vr/openvr/openvr_type_converters.h"
-
-#include <math.h>
-#include <iterator>
-#include <vector>
-
-#include "device/vr/public/mojom/vr_service.mojom.h"
-#include "third_party/openvr/src/headers/openvr.h"
-#include "ui/gfx/transform_util.h"
-
-namespace mojo {
-
-device::mojom::VRPosePtr
-TypeConverter<device::mojom::VRPosePtr, vr::TrackedDevicePose_t>::Convert(
- const vr::TrackedDevicePose_t& hmd_pose) {
- device::mojom::VRPosePtr pose = device::mojom::VRPose::New();
- pose->orientation = gfx::Quaternion();
- pose->position = gfx::Point3F();
-
- if (hmd_pose.bPoseIsValid && hmd_pose.bDeviceIsConnected) {
- const float(&m)[3][4] = hmd_pose.mDeviceToAbsoluteTracking.m;
-
- gfx::Transform transform = gfx::Transform(
- m[0][0], m[0][1], m[0][2], 0.0f, m[1][0], m[1][1], m[1][2], 0.0f,
- m[2][0], m[2][1], m[2][2], 0.0f, 0.0f, 0.0f, 0.0f, 1.0f);
- gfx::DecomposedTransform decomposed;
- if (gfx::DecomposeTransform(&decomposed, transform)) {
- pose->orientation = decomposed.quaternion;
- }
-
- pose->position->SetPoint(m[0][3], m[1][3], m[2][3]);
- }
-
- return pose;
-}
-
-} // namespace mojo
diff --git a/chromium/device/vr/openvr/openvr_type_converters.h b/chromium/device/vr/openvr/openvr_type_converters.h
deleted file mode 100644
index 55d2b5e8fac..00000000000
--- a/chromium/device/vr/openvr/openvr_type_converters.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-#ifndef DEVICE_VR_OPENVR_TYPE_CONVERTERS_H
-#define DEVICE_VR_OPENVR_TYPE_CONVERTERS_H
-
-#include "device/vr/public/mojom/vr_service.mojom.h"
-#include "third_party/openvr/src/headers/openvr.h"
-
-namespace mojo {
-
-template <>
-struct TypeConverter<device::mojom::VRPosePtr, vr::TrackedDevicePose_t> {
- static device::mojom::VRPosePtr Convert(
- const vr::TrackedDevicePose_t& hmd_pose);
-};
-
-} // namespace mojo
-
-#endif // DEVICE_VR_OPENVR_TYPE_CONVERTERS_H
diff --git a/chromium/device/vr/openxr/openxr_controller.cc b/chromium/device/vr/openxr/openxr_controller.cc
index 6c1c2df4be7..65c920cac54 100644
--- a/chromium/device/vr/openxr/openxr_controller.cc
+++ b/chromium/device/vr/openxr/openxr_controller.cc
@@ -68,6 +68,7 @@ XrResult OpenXrController::Initialize(
XrInstance instance,
XrSession session,
const OpenXRPathHelper* path_helper,
+ const OpenXrExtensionHelper& extension_helper,
std::map<XrPath, std::vector<XrActionSuggestedBinding>>* bindings) {
DCHECK(bindings);
type_ = type;
@@ -95,7 +96,7 @@ XrResult OpenXrController::Initialize(
RETURN_IF_XR_FAILED(InitializeControllerActions());
- SuggestBindings(bindings);
+ SuggestBindings(extension_helper, bindings);
RETURN_IF_XR_FAILED(InitializeControllerSpaces());
return XR_SUCCESS;
@@ -132,10 +133,23 @@ XrResult OpenXrController::InitializeControllerActions() {
}
XrResult OpenXrController::SuggestBindings(
+ const OpenXrExtensionHelper& extension_helper,
std::map<XrPath, std::vector<XrActionSuggestedBinding>>* bindings) const {
const std::string binding_prefix = GetTopLevelUserPath(type_);
for (auto interaction_profile : kOpenXrControllerInteractionProfiles) {
+ // If the interaction profile is defined by an extension, check it here,
+ // otherwise continue
+ const bool extension_required =
+ interaction_profile.required_extension != nullptr;
+ if (extension_required) {
+ const bool extension_enabled = extension_helper.ExtensionSupported(
+ interaction_profile.required_extension);
+ if (!extension_enabled) {
+ continue;
+ }
+ }
+
XrPath interaction_profile_path =
path_helper_->GetInteractionProfileXrPath(interaction_profile.type);
RETURN_IF_XR_FAILED(SuggestActionBinding(
diff --git a/chromium/device/vr/openxr/openxr_controller.h b/chromium/device/vr/openxr/openxr_controller.h
index 3997d09d975..3b6e7786444 100644
--- a/chromium/device/vr/openxr/openxr_controller.h
+++ b/chromium/device/vr/openxr/openxr_controller.h
@@ -34,6 +34,7 @@ class OpenXrController {
XrInstance instance,
XrSession session,
const OpenXRPathHelper* path_helper,
+ const OpenXrExtensionHelper& extension_helper,
std::map<XrPath, std::vector<XrActionSuggestedBinding>>* bindings);
XrActionSet action_set() const { return action_set_; }
@@ -61,6 +62,7 @@ class OpenXrController {
XrResult InitializeControllerSpaces();
XrResult SuggestBindings(
+ const OpenXrExtensionHelper& extension_helper,
std::map<XrPath, std::vector<XrActionSuggestedBinding>>* bindings) const;
XrResult CreateActionsForButton(OpenXrButtonType button_type);
diff --git a/chromium/device/vr/openxr/openxr_defs.h b/chromium/device/vr/openxr/openxr_defs.h
index 0ca8d86a720..54ddb8b4add 100644
--- a/chromium/device/vr/openxr/openxr_defs.h
+++ b/chromium/device/vr/openxr/openxr_defs.h
@@ -9,6 +9,11 @@ namespace device {
constexpr char kWin32AppcontainerCompatibleExtensionName[] =
"XR_EXT_win32_appcontainer_compatible";
+constexpr char kExtSamsungOdysseyControllerExtensionName[] =
+ "XR_EXT_samsung_odyssey_controller";
+constexpr char kExtHPMixedRealityControllerExtensionName[] =
+ "XR_EXT_hp_mixed_reality_controller";
+
} // namespace device
#endif // DEVICE_VR_OPENXR_OPENXR_DEFS_H_
diff --git a/chromium/device/vr/openxr/openxr_device.cc b/chromium/device/vr/openxr/openxr_device.cc
index dd2542e7860..bab86430a87 100644
--- a/chromium/device/vr/openxr/openxr_device.cc
+++ b/chromium/device/vr/openxr/openxr_device.cc
@@ -27,11 +27,9 @@ constexpr unsigned int kRenderHeight = 1024;
// However our mojo interface expects display info right away to support WebVR.
// We create a fake display info to use, then notify the client that the display
// info changed when we get real data.
-mojom::VRDisplayInfoPtr CreateFakeVRDisplayInfo(device::mojom::XRDeviceId id) {
+mojom::VRDisplayInfoPtr CreateFakeVRDisplayInfo() {
mojom::VRDisplayInfoPtr display_info = mojom::VRDisplayInfo::New();
- display_info->id = id;
-
display_info->left_eye = mojom::VREyeParameters::New();
display_info->right_eye = mojom::VREyeParameters::New();
@@ -61,7 +59,7 @@ OpenXrDevice::OpenXrDevice(OpenXrStatics* openxr_statics)
: VRDeviceBase(device::mojom::XRDeviceId::OPENXR_DEVICE_ID),
instance_(openxr_statics->GetXrInstance()),
weak_ptr_factory_(this) {
- mojom::VRDisplayInfoPtr display_info = CreateFakeVRDisplayInfo(GetId());
+ mojom::VRDisplayInfoPtr display_info = CreateFakeVRDisplayInfo();
SetVRDisplayInfo(std::move(display_info));
#if defined(OS_WIN)
diff --git a/chromium/device/vr/openxr/openxr_input_helper.cc b/chromium/device/vr/openxr/openxr_input_helper.cc
index 8f910588ec9..668faadcfe2 100644
--- a/chromium/device/vr/openxr/openxr_input_helper.cc
+++ b/chromium/device/vr/openxr/openxr_input_helper.cc
@@ -122,10 +122,11 @@ XrResult OpenXRInputHelper::Initialize(XrInstance instance) {
// on availability.
std::map<XrPath, std::vector<XrActionSuggestedBinding>> bindings;
+ OpenXrExtensionHelper extension_helper;
for (size_t i = 0; i < controller_states_.size(); i++) {
RETURN_IF_XR_FAILED(controller_states_[i].controller.Initialize(
static_cast<OpenXrHandednessType>(i), instance, session_,
- path_helper_.get(), &bindings));
+ path_helper_.get(), extension_helper, &bindings));
controller_states_[i].primary_button_pressed = false;
}
diff --git a/chromium/device/vr/openxr/openxr_interaction_profiles.h b/chromium/device/vr/openxr/openxr_interaction_profiles.h
index 0cd071f37ab..9c32aa9fca9 100644
--- a/chromium/device/vr/openxr/openxr_interaction_profiles.h
+++ b/chromium/device/vr/openxr/openxr_interaction_profiles.h
@@ -7,6 +7,7 @@
#include "base/stl_util.h"
#include "device/gamepad/public/cpp/gamepad.h"
+#include "device/vr/openxr/openxr_defs.h"
#include "third_party/openxr/src/include/openxr/openxr.h"
namespace device {
@@ -25,7 +26,9 @@ enum class OpenXrInteractionProfileType {
kOculusTouch = 2,
kValveIndex = 3,
kHTCVive = 4,
- kCount = 5,
+ kSamsungOdyssey = 5,
+ kHPReverbG2 = 6,
+ kCount = 7,
};
enum class OpenXrButtonType {
@@ -71,6 +74,7 @@ struct OpenXrAxisPathMap {
struct OpenXrControllerInteractionProfile {
OpenXrInteractionProfileType type;
const char* const path;
+ const char* const required_extension;
GamepadMapping mapping;
const char* const* const input_profiles;
const size_t profile_size;
@@ -82,13 +86,14 @@ struct OpenXrControllerInteractionProfile {
size_t axis_map_size;
};
-// TODO(crbug.com/1017513)
// Currently Supports:
// Microsoft motion controller.
+// Samsung Odyssey controller
// Khronos simple controller.
// Oculus touch controller.
// Valve index controller.
// HTC vive controller
+// HP Reverb G2 controller
// Declare OpenXR input profile bindings for other runtimes when they become
// available.
constexpr const char* kMicrosoftMotionInputProfiles[] = {
@@ -105,6 +110,13 @@ constexpr const char* kValveIndexInputProfiles[] = {
constexpr const char* kHTCViveInputProfiles[] = {
"htc-vive", "generic-trigger-squeeze-touchpad"};
+constexpr const char* kSamsungOdysseyInputProfiles[] = {
+ "samsung-odyssey", "windows-mixed-reality",
+ "generic-trigger-squeeze-touchpad-thumbstick"};
+
+constexpr const char* kHPReverbG2InputProfiles[] = {
+ "hp-mixed-reality", "oculus-touch", "generic-trigger-squeeze"};
+
constexpr OpenXrButtonPathMap kMicrosoftMotionControllerButtonPathMaps[] = {
{OpenXrButtonType::kTrigger,
{
@@ -205,7 +217,7 @@ constexpr OpenXrButtonPathMap kValveIndexControllerButtonPathMaps[] = {
{{OpenXrButtonActionType::kPress, "/input/a/click"},
{OpenXrButtonActionType::kTouch, "/input/a/touch"}},
2},
-}; // namespace device
+};
constexpr OpenXrButtonPathMap kHTCViveControllerButtonPathMaps[] = {
{OpenXrButtonType::kTrigger,
@@ -222,6 +234,46 @@ constexpr OpenXrButtonPathMap kHTCViveControllerButtonPathMaps[] = {
{OpenXrButtonActionType::kTouch, "/input/trackpad/touch"}},
2}};
+constexpr OpenXrButtonPathMap kHPReverbG2LeftControllerButtonPathMaps[] = {
+ {OpenXrButtonType::kTrigger,
+ {{OpenXrButtonActionType::kPress, "/input/trigger/value"},
+ {OpenXrButtonActionType::kValue, "/input/trigger/value"}},
+ 2},
+ {OpenXrButtonType::kSqueeze,
+ {{OpenXrButtonActionType::kPress, "/input/squeeze/value"},
+ {OpenXrButtonActionType::kValue, "/input/squeeze/value"}},
+ 2},
+ {OpenXrButtonType::kThumbstick,
+ {{OpenXrButtonActionType::kPress, "/input/thumbstick/click"}},
+ 1},
+ {OpenXrButtonType::kButton1,
+ {{OpenXrButtonActionType::kPress, "/input/x/click"}},
+ 1},
+ {OpenXrButtonType::kButton2,
+ {{OpenXrButtonActionType::kPress, "/input/y/click"}},
+ 1},
+};
+
+constexpr OpenXrButtonPathMap kHPReverbG2RightControllerButtonPathMaps[] = {
+ {OpenXrButtonType::kTrigger,
+ {{OpenXrButtonActionType::kPress, "/input/trigger/value"},
+ {OpenXrButtonActionType::kValue, "/input/trigger/value"}},
+ 2},
+ {OpenXrButtonType::kSqueeze,
+ {{OpenXrButtonActionType::kPress, "/input/squeeze/value"},
+ {OpenXrButtonActionType::kValue, "/input/squeeze/value"}},
+ 2},
+ {OpenXrButtonType::kThumbstick,
+ {{OpenXrButtonActionType::kPress, "/input/thumbstick/click"}},
+ 1},
+ {OpenXrButtonType::kButton1,
+ {{OpenXrButtonActionType::kPress, "/input/a/click"}},
+ 1},
+ {OpenXrButtonType::kButton2,
+ {{OpenXrButtonActionType::kPress, "/input/b/click"}},
+ 1},
+};
+
constexpr OpenXrAxisPathMap kMicrosoftMotionControllerAxisPathMaps[] = {
{OpenXrAxisType::kTrackpad, "/input/trackpad"},
{OpenXrAxisType::kThumbstick, "/input/thumbstick"},
@@ -240,10 +292,15 @@ constexpr OpenXrAxisPathMap kHTCViveControllerAxisPathMaps[] = {
{OpenXrAxisType::kTrackpad, "/input/trackpad"},
};
+constexpr OpenXrAxisPathMap kHPReverbG2ControllerAxisPathMaps[] = {
+ {OpenXrAxisType::kThumbstick, "/input/thumbstick"},
+};
+
constexpr OpenXrControllerInteractionProfile
kMicrosoftMotionInteractionProfile = {
OpenXrInteractionProfileType::kMicrosoftMotion,
"/interaction_profiles/microsoft/motion_controller",
+ nullptr,
GamepadMapping::kXrStandard,
kMicrosoftMotionInputProfiles,
base::size(kMicrosoftMotionInputProfiles),
@@ -257,6 +314,7 @@ constexpr OpenXrControllerInteractionProfile
constexpr OpenXrControllerInteractionProfile kKHRSimpleInteractionProfile = {
OpenXrInteractionProfileType::kKHRSimple,
"/interaction_profiles/khr/simple_controller",
+ nullptr,
GamepadMapping::kNone,
kGenericButtonInputProfiles,
base::size(kGenericButtonInputProfiles),
@@ -270,6 +328,7 @@ constexpr OpenXrControllerInteractionProfile kKHRSimpleInteractionProfile = {
constexpr OpenXrControllerInteractionProfile kOculusTouchInteractionProfile = {
OpenXrInteractionProfileType::kOculusTouch,
"/interaction_profiles/oculus/touch_controller",
+ nullptr,
GamepadMapping::kXrStandard,
kOculusTouchInputProfiles,
base::size(kOculusTouchInputProfiles),
@@ -283,6 +342,7 @@ constexpr OpenXrControllerInteractionProfile kOculusTouchInteractionProfile = {
constexpr OpenXrControllerInteractionProfile kValveIndexInteractionProfile = {
OpenXrInteractionProfileType::kValveIndex,
"/interaction_profiles/valve/index_controller",
+ nullptr,
GamepadMapping::kXrStandard,
kValveIndexInputProfiles,
base::size(kValveIndexInputProfiles),
@@ -296,6 +356,7 @@ constexpr OpenXrControllerInteractionProfile kValveIndexInteractionProfile = {
constexpr OpenXrControllerInteractionProfile kHTCViveInteractionProfile = {
OpenXrInteractionProfileType::kHTCVive,
"/interaction_profiles/htc/vive_controller",
+ nullptr,
GamepadMapping::kXrStandard,
kHTCViveInputProfiles,
base::size(kHTCViveInputProfiles),
@@ -306,11 +367,40 @@ constexpr OpenXrControllerInteractionProfile kHTCViveInteractionProfile = {
kHTCViveControllerAxisPathMaps,
base::size(kHTCViveControllerAxisPathMaps)};
+constexpr OpenXrControllerInteractionProfile kSamsungOdysseyInteractionProfile =
+ {OpenXrInteractionProfileType::kSamsungOdyssey,
+ "/interaction_profiles/samsung/odyssey_controller",
+ kExtSamsungOdysseyControllerExtensionName,
+ GamepadMapping::kXrStandard,
+ kSamsungOdysseyInputProfiles,
+ base::size(kSamsungOdysseyInputProfiles),
+ kMicrosoftMotionControllerButtonPathMaps,
+ base::size(kMicrosoftMotionControllerButtonPathMaps),
+ kMicrosoftMotionControllerButtonPathMaps,
+ base::size(kMicrosoftMotionControllerButtonPathMaps),
+ kMicrosoftMotionControllerAxisPathMaps,
+ base::size(kMicrosoftMotionControllerAxisPathMaps)};
+
+constexpr OpenXrControllerInteractionProfile kHPReverbG2InteractionProfile = {
+ OpenXrInteractionProfileType::kHPReverbG2,
+ "/interaction_profiles/hp/mixed_reality_controller",
+ kExtHPMixedRealityControllerExtensionName,
+ GamepadMapping::kXrStandard,
+ kHPReverbG2InputProfiles,
+ base::size(kHPReverbG2InputProfiles),
+ kHPReverbG2LeftControllerButtonPathMaps,
+ base::size(kHPReverbG2LeftControllerButtonPathMaps),
+ kHPReverbG2RightControllerButtonPathMaps,
+ base::size(kHPReverbG2RightControllerButtonPathMaps),
+ kHPReverbG2ControllerAxisPathMaps,
+ base::size(kHPReverbG2ControllerAxisPathMaps)};
+
constexpr OpenXrControllerInteractionProfile
kOpenXrControllerInteractionProfiles[] = {
kMicrosoftMotionInteractionProfile, kKHRSimpleInteractionProfile,
- kOculusTouchInteractionProfile, kValveIndexInteractionProfile,
- kHTCViveInteractionProfile};
+ kOculusTouchInteractionProfile, kValveIndexInteractionProfile,
+ kHTCViveInteractionProfile, kSamsungOdysseyInteractionProfile,
+ kHPReverbG2InteractionProfile};
} // namespace device
diff --git a/chromium/device/vr/openxr/openxr_render_loop.cc b/chromium/device/vr/openxr/openxr_render_loop.cc
index 6d37f401340..de8f53b5e1d 100644
--- a/chromium/device/vr/openxr/openxr_render_loop.cc
+++ b/chromium/device/vr/openxr/openxr_render_loop.cc
@@ -168,8 +168,6 @@ void OpenXrRenderLoop::InitializeDisplayInfo() {
current_display_info_->left_eye = mojom::VREyeParameters::New();
}
- current_display_info_->id = device::mojom::XRDeviceId::OPENXR_DEVICE_ID;
-
gfx::Size view_size = openxr_->GetViewSize();
current_display_info_->left_eye->render_width = view_size.width();
current_display_info_->right_eye->render_width = view_size.width();
diff --git a/chromium/device/vr/openxr/openxr_util.cc b/chromium/device/vr/openxr/openxr_util.cc
index 89b5a2273eb..82d38060c79 100644
--- a/chromium/device/vr/openxr/openxr_util.cc
+++ b/chromium/device/vr/openxr/openxr_util.cc
@@ -70,7 +70,8 @@ OpenXrExtensionHelper::OpenXrExtensionHelper() {
OpenXrExtensionHelper::~OpenXrExtensionHelper() = default;
-bool OpenXrExtensionHelper::ExtensionSupported(const char* extension_name) {
+bool OpenXrExtensionHelper::ExtensionSupported(
+ const char* extension_name) const {
return std::find_if(
extension_properties_.begin(), extension_properties_.end(),
[&extension_name](const XrExtensionProperties& properties) {
@@ -134,6 +135,22 @@ XrResult CreateInstance(XrInstance* instance) {
extensions.push_back(XR_MSFT_UNBOUNDED_REFERENCE_SPACE_EXTENSION_NAME);
}
+ // Input extensions. These enable interaction profiles not defined in the core
+ // spec
+ const bool samsungInteractionProfileExtensionSupported =
+ extension_helper.ExtensionSupported(
+ kExtSamsungOdysseyControllerExtensionName);
+ if (samsungInteractionProfileExtensionSupported) {
+ extensions.push_back(kExtSamsungOdysseyControllerExtensionName);
+ }
+
+ const bool hpControllerExtensionSupported =
+ extension_helper.ExtensionSupported(
+ kExtHPMixedRealityControllerExtensionName);
+ if (hpControllerExtensionSupported) {
+ extensions.push_back(kExtHPMixedRealityControllerExtensionName);
+ }
+
instance_create_info.enabledExtensionCount =
static_cast<uint32_t>(extensions.size());
instance_create_info.enabledExtensionNames = extensions.data();
diff --git a/chromium/device/vr/openxr/openxr_util.h b/chromium/device/vr/openxr/openxr_util.h
index 9ba08da712c..31d55162b32 100644
--- a/chromium/device/vr/openxr/openxr_util.h
+++ b/chromium/device/vr/openxr/openxr_util.h
@@ -16,7 +16,7 @@ class OpenXrExtensionHelper {
OpenXrExtensionHelper();
~OpenXrExtensionHelper();
- bool ExtensionSupported(const char* extension_name);
+ bool ExtensionSupported(const char* extension_name) const;
private:
std::vector<XrExtensionProperties> extension_properties_;
diff --git a/chromium/device/vr/orientation/orientation_device.cc b/chromium/device/vr/orientation/orientation_device.cc
index a69e9278ecf..0c1ca109e98 100644
--- a/chromium/device/vr/orientation/orientation_device.cc
+++ b/chromium/device/vr/orientation/orientation_device.cc
@@ -27,12 +27,6 @@ using gfx::Vector3dF;
namespace {
static constexpr int kDefaultPumpFrequencyHz = 60;
-mojom::VRDisplayInfoPtr CreateVRDisplayInfo(mojom::XRDeviceId id) {
- mojom::VRDisplayInfoPtr display_info = mojom::VRDisplayInfo::New();
- display_info->id = id;
- return display_info;
-}
-
display::Display::Rotation GetRotation() {
display::Screen* screen = display::Screen::GetScreen();
if (!screen) {
@@ -54,7 +48,7 @@ VROrientationDevice::VROrientationDevice(mojom::SensorProvider* sensor_provider,
base::BindOnce(&VROrientationDevice::SensorReady,
base::Unretained(this)));
- SetVRDisplayInfo(CreateVRDisplayInfo(GetId()));
+ SetVRDisplayInfo(mojom::VRDisplayInfo::New());
}
VROrientationDevice::~VROrientationDevice() {
@@ -170,10 +164,6 @@ void VROrientationDevice::GetInlineFrameData(
mojom::XRFrameDataProvider::GetFrameDataCallback callback) {
// Orientation sessions should never be exclusive or presenting.
DCHECK(!HasExclusiveSession());
- if (!inline_poses_enabled_) {
- std::move(callback).Run(nullptr);
- return;
- }
mojom::VRPosePtr pose = mojom::VRPose::New();
diff --git a/chromium/device/vr/orientation/orientation_device_unittest.cc b/chromium/device/vr/orientation/orientation_device_unittest.cc
index 3667a9f3c74..7bd19b9f26e 100644
--- a/chromium/device/vr/orientation/orientation_device_unittest.cc
+++ b/chromium/device/vr/orientation/orientation_device_unittest.cc
@@ -164,10 +164,6 @@ class VROrientationDeviceTest : public testing::Test {
}
}
- void SetInlinePosesEnabled(bool enabled) {
- device_->SetInlinePosesEnabled(enabled);
- }
-
std::unique_ptr<VROrientationSession> MakeDisplay() {
mojo::PendingRemote<mojom::XRFrameDataProvider> data_provider;
mojo::PendingRemote<mojom::XRSessionController> controller;
@@ -383,14 +379,6 @@ TEST_F(VROrientationDeviceTest, OrientationLandscape270Test) {
}));
}
-TEST_F(VROrientationDeviceTest, NoMagicWindowPosesWhileBrowsing) {
- InitializeDevice(FakeInitParams());
-
- AssertInlineFrameDataAvailable(true);
- SetInlinePosesEnabled(false);
- AssertInlineFrameDataAvailable(false);
-}
-
TEST_F(VROrientationDeviceTest, GetFrameDataHelper) {
InitializeDevice(FakeInitParams());
diff --git a/chromium/device/vr/public/mojom/BUILD.gn b/chromium/device/vr/public/mojom/BUILD.gn
index 56752b7b348..3f887e4eab8 100644
--- a/chromium/device/vr/public/mojom/BUILD.gn
+++ b/chromium/device/vr/public/mojom/BUILD.gn
@@ -28,9 +28,6 @@ mojom_component("mojom") {
if (enable_oculus_vr) {
enabled_features += [ "enable_oculus_vr" ]
}
- if (enable_openvr) {
- enabled_features += [ "enable_openvr" ]
- }
if (enable_windows_mr) {
enabled_features += [ "enable_windows_mr" ]
}
diff --git a/chromium/device/vr/public/mojom/README.md b/chromium/device/vr/public/mojom/README.md
index 3b621bb8d71..f78552e3f1c 100644
--- a/chromium/device/vr/public/mojom/README.md
+++ b/chromium/device/vr/public/mojom/README.md
@@ -85,5 +85,5 @@ on the platform.
# Test interfaces (defined in browser_test_interfaces.mojom)
XRTestHook allows a test to control the behavior of a fake implementation of
-OpenVR, and potentially other runtimes. This allows testing the entire stack
+OpenXR, and potentially other runtimes. This allows testing the entire stack
of Chromium WebXR code end-to-end.
diff --git a/chromium/device/vr/public/mojom/isolated_xr_service.mojom b/chromium/device/vr/public/mojom/isolated_xr_service.mojom
index 12235c2a141..842ad9865a2 100644
--- a/chromium/device/vr/public/mojom/isolated_xr_service.mojom
+++ b/chromium/device/vr/public/mojom/isolated_xr_service.mojom
@@ -74,8 +74,21 @@ interface XRRuntime {
ListenToDeviceChanges(
pending_associated_remote<XRRuntimeEventListener> listener) =>
(VRDisplayInfo? display_info);
+};
- SetInlinePosesEnabled(bool enable);
+// Information required for rendering, used by ImmersiveOverlay.
+// TODO(https://crbug.com/1126608): This struct currently contains subset of
+// data contained by XRFrameData, move it to vr_service.mojom and refactor
+// XRFrameData so that it embeds this struct.
+struct XRRenderInfo
+{
+ // The frame_id maps frame data to a frame arriving from the compositor. IDs
+ // will be reused after the frame arrives from the compositor. Negative IDs
+ // imply no mapping.
+ int16 frame_id;
+
+ // The pose may be null if the device lost tracking.
+ VRPose? pose;
};
// Represents an overlay that the browser may show on top of or instead of WebXR
@@ -84,7 +97,7 @@ interface XRRuntime {
interface ImmersiveOverlay {
// Request a pose. If there is WebXR and an overlay visible at the same time,
// the same pose will be given to both.
- RequestNextOverlayPose() => (device.mojom.XRFrameData pose);
+ RequestNextOverlayPose() => (XRRenderInfo render_info);
// Submit a frame to show in the headset. Only can be called when an overlay
// is visible. The frame will be composited on top of WebXR content.
diff --git a/chromium/device/vr/public/mojom/vr_service.mojom b/chromium/device/vr/public/mojom/vr_service.mojom
index 0d8c7ac22ac..32ce3b521ee 100644
--- a/chromium/device/vr/public/mojom/vr_service.mojom
+++ b/chromium/device/vr/public/mojom/vr_service.mojom
@@ -5,6 +5,7 @@
module device.mojom;
import "device/gamepad/public/mojom/gamepad.mojom";
+import "mojo/public/mojom/base/big_buffer.mojom";
import "mojo/public/mojom/base/time.mojom";
import "gpu/ipc/common/mailbox_holder.mojom";
import "gpu/ipc/common/sync_token.mojom";
@@ -22,13 +23,13 @@ import "ui/gfx/mojom/transform.mojom";
// TODO(https://crbug.com/966099): Use EnableIf to only define values on
// platforms that have implementations.
-// XRDeviceId is used in metrics, so don't reorder.
+// XRDeviceId is used in metrics, so don't reorder or reuse.
enum XRDeviceId {
WEB_TEST_DEVICE_ID = 0, // Fake device used by web_tests.
FAKE_DEVICE_ID = 1, // Fake device used in unit tests.
ORIENTATION_DEVICE_ID = 2,
GVR_DEVICE_ID = 3,
- [EnableIf=enable_openvr] OPENVR_DEVICE_ID = 4,
+ // OPENVR_DEVICE_ID = 4,
[EnableIf=enable_oculus_vr] OCULUS_DEVICE_ID = 5,
[EnableIf=enable_windows_mr] WINDOWS_MIXED_REALITY_ID = 6,
ARCORE_DEVICE_ID = 7,
@@ -62,6 +63,7 @@ enum XRSessionFeature {
ANCHORS = 9,
CAMERA_ACCESS = 10, // Experimental feature.
PLANE_DETECTION = 11, // Experimental feature.
+ DEPTH = 12, // Experimental feature.
};
// These values are persisted to logs. Entries should not be renumbered and
@@ -127,6 +129,10 @@ struct XRSession {
// Indicates whether the device backing this session sends input events solely
// via eventing (as opposed to polling).
bool uses_input_eventing;
+
+ // The default scale that should be applied to the native framebuffer size
+ // unless overridden by the developer.
+ float default_framebuffer_scale = 1.0;
};
// This structure contains the infomation and interfaces needed to create a two
@@ -296,13 +302,11 @@ struct VRStageParameters {
};
struct VRDisplayInfo {
- XRDeviceId id;
VRStageParameters? stage_parameters;
// Parameters required to distort a scene for viewing in a VR headset. Only
// required for devices which have the can_present capability.
VREyeParameters? left_eye;
VREyeParameters? right_eye;
- float webxr_default_framebuffer_scale = 1.0;
};
// Frame transport method from the Renderer's point of view.
@@ -535,6 +539,36 @@ struct XRLightEstimationData {
XRReflectionProbe? reflection_probe;
};
+// Structure that signifies that the depth data is still valid, no additional
+// information is provided. See |XRDepthData| for more details.
+struct XRDepthDataStillValid {};
+
+// Structure that signifies that the Depth data was updated. Provides
+// information about current depth data. See |XRDepthData| for more details.
+struct XRDepthDataUpdated {
+ // Timestamp of the returned data, in unspecified base.
+ mojo_base.mojom.TimeDelta time_delta;
+ // Array of 16-bit numbers representing depth in millimeters from the camera
+ // plane.
+ mojo_base.mojom.BigBuffer pixel_data;
+ // Transform that needs to be applied when indexing into pixel_data when using
+ // normalized view coordinates (with origin at bottom left corner of the
+ // screen).
+ gfx.mojom.Transform norm_texture_from_norm_view;
+ // Size of the pixel array. Valid iff pixel_data is not null.
+ gfx.mojom.Size size;
+};
+
+// Depth data may be the same as the one returned in the previous frame - it is
+// represented as a union of (empty) XRDepthDataStillValid structure that
+// conveys no additional information except that the previously returned depth
+// data is still valid, and the XRDepthDataUpdated structure that signifies that
+// the depth data was updated & carries all information about it.
+union XRDepthData {
+ XRDepthDataStillValid data_still_valid;
+ XRDepthDataUpdated updated_depth_data;
+};
+
// The data needed for each animation frame of an XRSession.
struct XRFrameData {
// General XRSession value
@@ -547,10 +581,18 @@ struct XRFrameData {
// The buffer_holder is used for sending imagery data back and forth across
// the process boundary.
gpu.mojom.MailboxHolder? buffer_holder;
+
// The camera_image_buffer_holder is used to send a copy of the camera image
- // across the process boundary for immersive-ar sessions.
+ // across the process boundary for immersive-ar sessions. This assumes that
+ // there exists a single camera.
gpu.mojom.MailboxHolder? camera_image_buffer_holder;
+ // Depth data (if the device supports it and the environment integration is
+ // enabled). This assumes that depth information is provided from a single
+ // sensor. If for any reason the latest depth data could not be obtained, it
+ // will be set to null.
+ XRDepthData? depth_data;
+
// Indicates that there has been a significant discontinuity in the mojo space
// coordinate system, and that poses from this point forward with the same
// coordinates as those received previously may not actually be in the same
@@ -660,10 +702,11 @@ union RequestSessionResult {
// process is compatible with the active VR headset.
enum XrCompatibleResult {
// Compatibility results where the GPU process was not restarted.
- kAlreadyCompatible,
- kNotCompatible,
+ kAlreadyCompatible, // GPU process is already on a compatible GPU.
+ kNoDeviceAvailable, // No WebXR device is available.
+ kWebXrFeaturePolicyBlocked, // WebXR is blocked by feature policy.
- // Compatibility results where the GPU was restarted. Context lost and
+ // Compatibility results where the GPU process 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.
diff --git a/chromium/device/vr/test/test_hook.h b/chromium/device/vr/test/test_hook.h
index 6c2db8e236b..0d51c4a9211 100644
--- a/chromium/device/vr/test/test_hook.h
+++ b/chromium/device/vr/test/test_hook.h
@@ -14,7 +14,6 @@
namespace device {
// Update this string whenever either interface changes.
-constexpr char kChromeOpenVRTestHookAPI[] = "ChromeTestHook_3";
constexpr unsigned int kMaxTrackedDevices = 64;
constexpr unsigned int kMaxNumAxes = 5;
diff --git a/chromium/device/vr/vr_device.h b/chromium/device/vr/vr_device.h
index 5193d1b212f..99bf6a5480b 100644
--- a/chromium/device/vr/vr_device.h
+++ b/chromium/device/vr/vr_device.h
@@ -21,9 +21,9 @@ enum class VrViewerType {
GVR_DAYDREAM = 2,
ORIENTATION_SENSOR_DEVICE = 10,
FAKE_DEVICE = 11,
- OPENVR_UNKNOWN = 20,
- OPENVR_VIVE = 21,
- OPENVR_RIFT_CV1 = 22,
+ // OPENVR_UNKNOWN = 20,
+ // OPENVR_VIVE = 21,
+ // OPENVR_RIFT_CV1 = 22,
OCULUS_UNKNOWN = 40, // Going through Oculus APIs
WINDOWS_MIXED_REALITY_UNKNOWN = 60, // Going through WMR APIs
OPENXR_UNKNOWN = 70, // Going through OpenXR APIs
diff --git a/chromium/device/vr/vr_device_base.cc b/chromium/device/vr/vr_device_base.cc
index 9d113bb653d..d72cb0980f4 100644
--- a/chromium/device/vr/vr_device_base.cc
+++ b/chromium/device/vr/vr_device_base.cc
@@ -66,7 +66,6 @@ void VRDeviceBase::ListenToDeviceChanges(
void VRDeviceBase::SetVRDisplayInfo(mojom::VRDisplayInfoPtr display_info) {
DCHECK(display_info);
- DCHECK(display_info->id == id_);
display_info_ = std::move(display_info);
if (listener_)
@@ -93,10 +92,6 @@ mojo::PendingRemote<mojom::XRRuntime> VRDeviceBase::BindXRRuntime() {
return runtime_receiver_.BindNewPipeAndPassRemote();
}
-void VRDeviceBase::SetInlinePosesEnabled(bool enable) {
- inline_poses_enabled_ = enable;
-}
-
void LogViewerType(VrViewerType type) {
base::UmaHistogramSparse("VRViewerType", static_cast<int>(type));
}
diff --git a/chromium/device/vr/vr_device_base.h b/chromium/device/vr/vr_device_base.h
index c16a63ca192..b5e9ef8b8e6 100644
--- a/chromium/device/vr/vr_device_base.h
+++ b/chromium/device/vr/vr_device_base.h
@@ -32,7 +32,6 @@ class COMPONENT_EXPORT(DEVICE_VR_BASE) VRDeviceBase : public mojom::XRRuntime {
void ListenToDeviceChanges(
mojo::PendingAssociatedRemote<mojom::XRRuntimeEventListener> listener,
mojom::XRRuntime::ListenToDeviceChangesCallback callback) final;
- void SetInlinePosesEnabled(bool enable) override;
void ShutdownSession(mojom::XRRuntime::ShutdownSessionCallback) override;
device::mojom::XRDeviceId GetId() const;
@@ -69,8 +68,6 @@ class COMPONENT_EXPORT(DEVICE_VR_BASE) VRDeviceBase : public mojom::XRRuntime {
mojom::VRDisplayInfoPtr display_info_;
- bool inline_poses_enabled_ = true;
-
private:
mojo::AssociatedRemote<mojom::XRRuntimeEventListener> listener_;
diff --git a/chromium/device/vr/vr_device_base_unittest.cc b/chromium/device/vr/vr_device_base_unittest.cc
index 7a911a2fa4e..c72e8dd4b49 100644
--- a/chromium/device/vr/vr_device_base_unittest.cc
+++ b/chromium/device/vr/vr_device_base_unittest.cc
@@ -72,16 +72,10 @@ class VRDeviceTest : public testing::Test {
std::unique_ptr<VRDeviceBaseForTesting> MakeVRDevice() {
std::unique_ptr<VRDeviceBaseForTesting> device =
std::make_unique<VRDeviceBaseForTesting>();
- device->SetVRDisplayInfoForTest(MakeVRDisplayInfo(device->GetId()));
+ device->SetVRDisplayInfoForTest(mojom::VRDisplayInfo::New());
return device;
}
- mojom::VRDisplayInfoPtr MakeVRDisplayInfo(mojom::XRDeviceId device_id) {
- mojom::VRDisplayInfoPtr display_info = mojom::VRDisplayInfo::New();
- display_info->id = device_id;
- return display_info;
- }
-
base::test::SingleThreadTaskEnvironment task_environment_;
DISALLOW_COPY_AND_ASSIGN(VRDeviceTest);
@@ -99,7 +93,7 @@ TEST_F(VRDeviceTest, DeviceChangedDispatched) {
base::DoNothing()); // TODO: consider getting initial info
base::RunLoop().RunUntilIdle();
EXPECT_CALL(listener, DoOnChanged(testing::_)).Times(1);
- device->SetVRDisplayInfoForTest(MakeVRDisplayInfo(device->GetId()));
+ device->SetVRDisplayInfoForTest(mojom::VRDisplayInfo::New());
base::RunLoop().RunUntilIdle();
}
diff --git a/chromium/device/vr/vr_gl_util.cc b/chromium/device/vr/vr_gl_util.cc
new file mode 100644
index 00000000000..c289b690dcd
--- /dev/null
+++ b/chromium/device/vr/vr_gl_util.cc
@@ -0,0 +1,105 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "device/vr/vr_gl_util.h"
+
+#include "ui/gfx/transform.h"
+
+namespace vr {
+
+// This code is adapted from the GVR Treasure Hunt demo source.
+std::array<float, 16> MatrixToGLArray(const gfx::Transform& transform) {
+ std::array<float, 16> result;
+ transform.matrix().asColMajorf(result.data());
+ return result;
+}
+
+GLuint CompileShader(GLenum shader_type,
+ const std::string& shader_source,
+ std::string& error) {
+ GLuint shader_handle = glCreateShader(shader_type);
+ if (shader_handle != 0) {
+ // Pass in the shader source. No need to pass in a length for
+ // null-terminated input.
+ const char* source = shader_source.c_str();
+ glShaderSource(shader_handle, 1, &source, nullptr);
+ // Compile the shader.
+ glCompileShader(shader_handle);
+ // Get the compilation status.
+ GLint status = GL_FALSE;
+ glGetShaderiv(shader_handle, GL_COMPILE_STATUS, &status);
+ if (status == GL_FALSE) {
+ GLint info_log_length = 0;
+ glGetShaderiv(shader_handle, GL_INFO_LOG_LENGTH, &info_log_length);
+ GLchar* str_info_log = new GLchar[info_log_length + 1];
+ glGetShaderInfoLog(shader_handle, info_log_length, nullptr, str_info_log);
+ error = "Error compiling shader: ";
+ error += str_info_log;
+ delete[] str_info_log;
+ glDeleteShader(shader_handle);
+ shader_handle = 0;
+ }
+ } else {
+ error = "Could not create a shader handle (did not attempt compilation).";
+ }
+
+ return shader_handle;
+}
+
+GLuint CreateAndLinkProgram(GLuint vertext_shader_handle,
+ GLuint fragment_shader_handle,
+ std::string& error) {
+ GLuint program_handle = glCreateProgram();
+
+ if (program_handle != 0) {
+ // Bind the vertex shader to the program.
+ glAttachShader(program_handle, vertext_shader_handle);
+
+ // Bind the fragment shader to the program.
+ glAttachShader(program_handle, fragment_shader_handle);
+
+ // Link the two shaders together into a program.
+ glLinkProgram(program_handle);
+
+ // Get the link status.
+ GLint link_status = GL_FALSE;
+ glGetProgramiv(program_handle, GL_LINK_STATUS, &link_status);
+
+ // If the link failed, delete the program.
+ if (link_status == GL_FALSE) {
+ GLint info_log_length = 0;
+ glGetProgramiv(program_handle, GL_INFO_LOG_LENGTH, &info_log_length);
+
+ GLchar* str_info_log = new GLchar[info_log_length + 1];
+ glGetProgramInfoLog(program_handle, info_log_length, nullptr,
+ str_info_log);
+ error = "Error compiling program: ";
+ error += str_info_log;
+ delete[] str_info_log;
+ glDeleteProgram(program_handle);
+ program_handle = 0;
+ }
+ }
+
+ return program_handle;
+}
+
+void SetTexParameters(GLenum texture_type) {
+ glTexParameteri(texture_type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(texture_type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(texture_type, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+}
+
+void SetColorUniform(GLuint handle, SkColor c) {
+ glUniform4f(handle, SkColorGetR(c) / 255.0, SkColorGetG(c) / 255.0,
+ SkColorGetB(c) / 255.0, SkColorGetA(c) / 255.0);
+}
+
+void SetOpaqueColorUniform(GLuint handle, SkColor c) {
+ glUniform3f(handle, SkColorGetR(c) / 255.0, SkColorGetG(c) / 255.0,
+ SkColorGetB(c) / 255.0);
+}
+
+} // namespace vr
diff --git a/chromium/device/vr/vr_gl_util.h b/chromium/device/vr/vr_gl_util.h
new file mode 100644
index 00000000000..e84543df4d1
--- /dev/null
+++ b/chromium/device/vr/vr_gl_util.h
@@ -0,0 +1,56 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef DEVICE_VR_VR_GL_UTIL_H_
+#define DEVICE_VR_VR_GL_UTIL_H_
+
+#include <array>
+#include <string>
+
+#include "base/component_export.h"
+#include "device/vr/gl_bindings.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+#define SHADER(Src) "#version 100\n" #Src
+#define OEIE_SHADER(Src) \
+ "#version 100\n#extension GL_OES_EGL_image_external : require\n" #Src
+#define VOID_OFFSET(x) reinterpret_cast<void*>(x)
+
+namespace gfx {
+class Transform;
+} // namespace gfx
+
+namespace vr {
+
+COMPONENT_EXPORT(DEVICE_VR_BASE)
+std::array<float, 16> MatrixToGLArray(const gfx::Transform& matrix);
+
+// Compile a shader. This is intended for browser-internal shaders only,
+// don't use this for user-supplied arbitrary shaders since data is handed
+// directly to the GL driver without further sanity checks.
+COMPONENT_EXPORT(DEVICE_VR_BASE)
+GLuint CompileShader(GLenum shader_type,
+ const std::string& shader_source,
+ std::string& error);
+
+// Compile and link a program.
+COMPONENT_EXPORT(DEVICE_VR_BASE)
+GLuint CreateAndLinkProgram(GLuint vertex_shader_handle,
+ GLuint fragment_shader_handle,
+ std::string& error);
+
+// Sets default texture parameters given a texture type.
+COMPONENT_EXPORT(DEVICE_VR_BASE) void SetTexParameters(GLenum texture_type);
+
+// Sets color uniforms given an SkColor.
+COMPONENT_EXPORT(DEVICE_VR_BASE) void SetColorUniform(GLuint handle, SkColor c);
+
+// Sets color uniforms (but not alpha) given an SkColor. The alpha is assumed to
+// be 1.0 in this case.
+COMPONENT_EXPORT(DEVICE_VR_BASE)
+void SetOpaqueColorUniform(GLuint handle, SkColor c);
+
+} // namespace vr
+
+#endif // DEVICE_VR_VR_GL_UTIL_H_
diff --git a/chromium/device/vr/windows/compositor_base.cc b/chromium/device/vr/windows/compositor_base.cc
index 2f9004f45a9..87ea395ac59 100644
--- a/chromium/device/vr/windows/compositor_base.cc
+++ b/chromium/device/vr/windows/compositor_base.cc
@@ -17,6 +17,17 @@ namespace {
// Number of frames to use for sliding averages for pose timings,
// as used for estimating prediction times.
constexpr unsigned kSlidingAverageSize = 5;
+
+device::mojom::XRRenderInfoPtr GetRenderInfo(
+ const device::mojom::XRFrameData& frame_data) {
+ device::mojom::XRRenderInfoPtr result = device::mojom::XRRenderInfo::New();
+
+ result->frame_id = frame_data.frame_id;
+ result->pose = frame_data.pose.Clone();
+
+ return result;
+}
+
} // namespace
namespace device {
@@ -100,7 +111,8 @@ void XRCompositorCommon::SubmitFrameWithTextureHandle(
if (on_webxr_submitted_)
std::move(on_webxr_submitted_).Run();
- if (!pending_frame_ || pending_frame_->frame_data_->frame_id != frame_index) {
+ if (!pending_frame_ ||
+ pending_frame_->render_info_->frame_id != frame_index) {
// We weren't expecting a submitted frame. This can happen if WebXR was
// hidden by an overlay for some time.
if (submit_client_) {
@@ -283,8 +295,9 @@ void XRCompositorCommon::StartPendingFrame() {
pending_frame_->waiting_for_webxr_ = webxr_visible_;
pending_frame_->waiting_for_overlay_ = overlay_visible_;
pending_frame_->frame_data_ = GetNextFrameData();
- // pending_frame_->frame_data_ should never be null
+ // GetNextFrameData() should never return null:
DCHECK(pending_frame_->frame_data_);
+ pending_frame_->render_info_ = GetRenderInfo(*pending_frame_->frame_data_);
}
}
@@ -327,9 +340,9 @@ void XRCompositorCommon::GetFrameData(
// specifically the next gamepad callback request that's likely to
// have been sent during WaitGetPoses.
task_runner()->PostTask(
- FROM_HERE,
- base::BindOnce(&XRCompositorCommon::SendFrameData, base::Unretained(this),
- std::move(callback), pending_frame_->frame_data_.Clone()));
+ FROM_HERE, base::BindOnce(&XRCompositorCommon::SendFrameData,
+ base::Unretained(this), std::move(callback),
+ std::move(pending_frame_->frame_data_)));
next_frame_id_ += 1;
if (next_frame_id_ < 0) {
@@ -413,7 +426,7 @@ void XRCompositorCommon::RequestNextOverlayPose(
// Ensure we have a pending frame.
StartPendingFrame();
pending_frame_->overlay_has_pose_ = true;
- std::move(callback).Run(pending_frame_->frame_data_.Clone());
+ std::move(callback).Run(pending_frame_->render_info_->Clone());
}
void XRCompositorCommon::SetOverlayAndWebXRVisibility(bool overlay_visible,
diff --git a/chromium/device/vr/windows/compositor_base.h b/chromium/device/vr/windows/compositor_base.h
index a5f378d383b..375fc124843 100644
--- a/chromium/device/vr/windows/compositor_base.h
+++ b/chromium/device/vr/windows/compositor_base.h
@@ -149,7 +149,9 @@ class XRCompositorCommon : public base::Thread,
bool overlay_submitted_ = false;
bool waiting_for_webxr_ = false;
bool waiting_for_overlay_ = false;
+
mojom::XRFrameDataPtr frame_data_;
+ mojom::XRRenderInfoPtr render_info_;
base::TimeTicks sent_frame_data_time_;
base::TimeTicks submit_frame_time_;
diff --git a/chromium/device/vr/windows_mixed_reality/mixed_reality_device.cc b/chromium/device/vr/windows_mixed_reality/mixed_reality_device.cc
index 24fe7d01be0..4a1dde0d4de 100644
--- a/chromium/device/vr/windows_mixed_reality/mixed_reality_device.cc
+++ b/chromium/device/vr/windows_mixed_reality/mixed_reality_device.cc
@@ -27,9 +27,8 @@ namespace {
// However our mojo interface expects display info right away to support WebVR.
// We create a fake display info to use, then notify the client that the display
// info changed when we get real data.
-mojom::VRDisplayInfoPtr CreateFakeVRDisplayInfo(device::mojom::XRDeviceId id) {
+mojom::VRDisplayInfoPtr CreateFakeVRDisplayInfo() {
mojom::VRDisplayInfoPtr display_info = mojom::VRDisplayInfo::New();
- display_info->id = id;
display_info->left_eye = mojom::VREyeParameters::New();
display_info->right_eye = mojom::VREyeParameters::New();
@@ -56,7 +55,7 @@ mojom::VRDisplayInfoPtr CreateFakeVRDisplayInfo(device::mojom::XRDeviceId id) {
MixedRealityDevice::MixedRealityDevice()
: VRDeviceBase(device::mojom::XRDeviceId::WINDOWS_MIXED_REALITY_ID) {
- SetVRDisplayInfo(CreateFakeVRDisplayInfo(GetId()));
+ SetVRDisplayInfo(CreateFakeVRDisplayInfo());
}
MixedRealityDevice::~MixedRealityDevice() {
diff --git a/chromium/device/vr/windows_mixed_reality/mixed_reality_renderloop.cc b/chromium/device/vr/windows_mixed_reality/mixed_reality_renderloop.cc
index 7524e7b08af..77d83ab1917 100644
--- a/chromium/device/vr/windows_mixed_reality/mixed_reality_renderloop.cc
+++ b/chromium/device/vr/windows_mixed_reality/mixed_reality_renderloop.cc
@@ -671,8 +671,6 @@ bool MixedRealityRenderLoop::UpdateDisplayInfo() {
if (!current_display_info_) {
current_display_info_ = mojom::VRDisplayInfo::New();
- current_display_info_->id =
- device::mojom::XRDeviceId::WINDOWS_MIXED_REALITY_ID;
changed = true;
}