From 2dcf4a819b24d95ad2d2b09ce2359254cb50dc4f Mon Sep 17 00:00:00 2001 From: Jakob Bornecrantz <jakob@collabora.com> Date: Mon, 1 Jun 2020 09:54:15 +0100 Subject: [PATCH] st/oxr: Improve xrSuggestInteractionProfileBindings validation of input --- doc/changes/state_trackers/mr.377.md | 2 + src/xrt/state_trackers/oxr/CMakeLists.txt | 37 ++- src/xrt/state_trackers/oxr/bindings.json | 245 ++++++++++++++++++++ src/xrt/state_trackers/oxr/bindings.py | 175 ++++++++++++++ src/xrt/state_trackers/oxr/meson.build | 27 ++- src/xrt/state_trackers/oxr/oxr_api_action.c | 58 ++++- 6 files changed, 539 insertions(+), 5 deletions(-) create mode 100644 doc/changes/state_trackers/mr.377.md create mode 100644 src/xrt/state_trackers/oxr/bindings.json create mode 100755 src/xrt/state_trackers/oxr/bindings.py diff --git a/doc/changes/state_trackers/mr.377.md b/doc/changes/state_trackers/mr.377.md new file mode 100644 index 000000000..55313b7b2 --- /dev/null +++ b/doc/changes/state_trackers/mr.377.md @@ -0,0 +1,2 @@ +OpenXR: More correctly verify the interactive profile binding data, including +the given interactive profile is correct and the binding point is valid. diff --git a/src/xrt/state_trackers/oxr/CMakeLists.txt b/src/xrt/state_trackers/oxr/CMakeLists.txt index 6540cd4bf..9559c0d6b 100644 --- a/src/xrt/state_trackers/oxr/CMakeLists.txt +++ b/src/xrt/state_trackers/oxr/CMakeLists.txt @@ -1,7 +1,32 @@ # Copyright 2019-2020, Collabora, Ltd. # SPDX-License-Identifier: BSL-1.0 + +### +# Binding generation +# + +function(bindings_gen output) + add_custom_command(OUTPUT ${output} + COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/bindings.py + ${CMAKE_CURRENT_SOURCE_DIR}/bindings.json + ${output} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bindings.py + ${CMAKE_CURRENT_SOURCE_DIR}/bindings.json + ) +endfunction(bindings_gen) + +bindings_gen(${CMAKE_CURRENT_BINARY_DIR}/oxr_generated_bindings.h) +bindings_gen(${CMAKE_CURRENT_BINARY_DIR}/oxr_generated_bindings.c) + + +### +# Main code +# + set(OXR_SOURCE_FILES + ${CMAKE_CURRENT_BINARY_DIR}/oxr_generated_bindings.h + ${CMAKE_CURRENT_BINARY_DIR}/oxr_generated_bindings.c oxr_api_action.c oxr_api_funcs.h oxr_api_instance.c @@ -52,4 +77,14 @@ if(XRT_HAVE_EGL) endif() add_library(st_oxr STATIC ${OXR_SOURCE_FILES}) -target_link_libraries(st_oxr PRIVATE xrt-interfaces xrt-external-openxr aux_util aux_math Vulkan::Vulkan comp_client) +target_link_libraries(st_oxr PRIVATE + xrt-interfaces + xrt-external-openxr + aux_util + aux_math + Vulkan::Vulkan + comp_client + ) +target_include_directories(st_oxr PRIVATE + ${CMAKE_CURRENT_BINARY_DIR} + ) diff --git a/src/xrt/state_trackers/oxr/bindings.json b/src/xrt/state_trackers/oxr/bindings.json new file mode 100644 index 000000000..93647e9c8 --- /dev/null +++ b/src/xrt/state_trackers/oxr/bindings.json @@ -0,0 +1,245 @@ +{ + "/interaction_profiles/khr/simple_controller": { + "title": "Khronos Simple Controller", + "user_paths": [ + "/user/hand/left", + "/user/hand/right" + ], + "components": [ + {"subpath": "/input/select/click"}, + {"subpath": "/input/menu/click"}, + {"subpath": "/input/grip/pose"}, + {"subpath": "/input/aim/pose"}, + {"subpath": "/output/haptic"} + ] + }, + + "/interaction_profiles/google/daydream_controller": { + "title": "Google Daydream Controller", + "user_paths": [ + "/user/hand/left", + "/user/hand/right" + ], + "components": [ + {"subpath": "/input/select/click"}, + {"subpath": "/input/trackpad/x"}, + {"subpath": "/input/trackpad/y"}, + {"subpath": "/input/trackpad/click"}, + {"subpath": "/input/trackpad/touch"}, + {"subpath": "/input/grip/pose"}, + {"subpath": "/input/aim/pose"} + ] + }, + + "/interaction_profiles/htc/vive_controller": { + "title": "HTC Vive Controller", + "user_paths": [ + "/user/hand/left", + "/user/hand/right" + ], + "components": [ + {"subpath": "/input/system/click", "system": "true"}, + {"subpath": "/input/squeeze/click"}, + {"subpath": "/input/menu/click"}, + {"subpath": "/input/trigger/click"}, + {"subpath": "/input/trigger/value"}, + {"subpath": "/input/trackpad/x"}, + {"subpath": "/input/trackpad/y"}, + {"subpath": "/input/trackpad/click"}, + {"subpath": "/input/trackpad/touch"}, + {"subpath": "/input/grip/pose"}, + {"subpath": "/input/aim/pose"}, + {"subpath": "/output/haptic"} + ] + }, + + "/interaction_profiles/htc/vive_pro": { + "title": "HTC Vive Pro", + "user_paths": [ + "/user/head" + ], + "components": [ + {"subpath": "/input/system/click", "system": "true"}, + {"subpath": "/input/volume_up/click"}, + {"subpath": "/input/volume_down/click"}, + {"subpath": "/input/mute_mic/click"} + ] + }, + + "/interaction_profiles/microsoft/motion_controller": { + "title": "Microsoft Mixed Reality Motion Controller", + "user_paths": [ + "/user/hand/left", + "/user/hand/right" + ], + "components": [ + {"subpath": "/input/menu/click"}, + {"subpath": "/input/squeeze/click"}, + {"subpath": "/input/trigger/value"}, + {"subpath": "/input/thumbstick/x"}, + {"subpath": "/input/thumbstick/y"}, + {"subpath": "/input/thumbstick/click"}, + {"subpath": "/input/trackpad/x"}, + {"subpath": "/input/trackpad/y"}, + {"subpath": "/input/trackpad/click"}, + {"subpath": "/input/trackpad/touch"}, + {"subpath": "/input/grip/pose"}, + {"subpath": "/input/aim/pose"}, + {"subpath": "/output/haptic"} + ] + }, + + "/interaction_profiles/microsoft/xbox_controller": { + "title": "Microsoft Xbox Controller", + "user_paths": [ + "/user/gamepad" + ], + "components": [ + {"subpath": "/input/menu/click"}, + {"subpath": "/input/view/click"}, + {"subpath": "/input/a/click"}, + {"subpath": "/input/b/click"}, + {"subpath": "/input/x/click"}, + {"subpath": "/input/y/click"}, + {"subpath": "/input/dpad_down/click"}, + {"subpath": "/input/dpad_right/click"}, + {"subpath": "/input/dpad_up/click"}, + {"subpath": "/input/dpad_left/click"}, + {"subpath": "/input/shoulder_left/click"}, + {"subpath": "/input/shoulder_right/click"}, + {"subpath": "/input/thumbstick_left/click"}, + {"subpath": "/input/thumbstick_right/click"}, + {"subpath": "/input/trigger_left/value"}, + {"subpath": "/input/trigger_right/value"}, + {"subpath": "/input/thumbstick_left/x"}, + {"subpath": "/input/thumbstick_left/y"}, + {"subpath": "/input/thumbstick_right/x"}, + {"subpath": "/input/thumbstick_right/y"}, + {"subpath": "/output/haptic_left"}, + {"subpath": "/output/haptic_right"}, + {"subpath": "/output/haptic_left_trigger"}, + {"subpath": "/output/haptic_right_trigger"} + ] + }, + + "/interaction_profiles/oculus/go_controller": { + "title": "Oculus Go Controller", + "user_paths": [ + "/user/hand/left", + "/user/hand/right" + ], + "components": [ + {"subpath": "/input/system/click", "system": "true"}, + {"subpath": "/input/trigger/click"}, + {"subpath": "/input/back/click"}, + {"subpath": "/input/trackpad/x"}, + {"subpath": "/input/trackpad/y"}, + {"subpath": "/input/trackpad/click"}, + {"subpath": "/input/trackpad/touch"}, + {"subpath": "/input/grip/pose"}, + {"subpath": "/input/aim/pose"} + ] + }, + + "/interaction_profiles/oculus/touch_controller": { + "title": "Oculus Touch Controller", + "user_paths": [ + "/user/hand/left", + "/user/hand/right" + ], + "components": [ + {"user_paths": "/user/hand/left", "subpath": "/input/x/click"}, + {"user_paths": "/user/hand/left", "subpath": "/input/x/touch"}, + {"user_paths": "/user/hand/left", "subpath": "/input/y/click"}, + {"user_paths": "/user/hand/left", "subpath": "/input/y/touch"}, + {"user_paths": "/user/hand/left", "subpath": "/input/menu/click"}, + {"user_paths": "/user/hand/right", "subpath": "/input/a/click"}, + {"user_paths": "/user/hand/right", "subpath": "/input/a/touch"}, + {"user_paths": "/user/hand/right", "subpath": "/input/b/click"}, + {"user_paths": "/user/hand/right", "subpath": "/input/b/touch"}, + {"user_paths": "/user/hand/right", "subpath": "/input/system/click", "system": "true"}, + {"subpath": "/input/squeeze/value"}, + {"subpath": "/input/trigger/value"}, + {"subpath": "/input/trigger/touch"}, + {"subpath": "/input/thumbstick/x"}, + {"subpath": "/input/thumbstick/y"}, + {"subpath": "/input/thumbstick/click"}, + {"subpath": "/input/thumbstick/touch"}, + {"subpath": "/input/thumbrest/touch"}, + {"subpath": "/input/grip/pose"}, + {"subpath": "/input/aim/pose"}, + {"subpath": "/output/haptic"} + ] + }, + + "/interaction_profiles/valve/index_controller": { + "title": "Valve Index Controller", + "user_paths": [ + "/user/hand/left", + "/user/hand/right" + ], + "components": [ + {"subpath": "/input/system/click", "system": "true"}, + {"subpath": "/input/system/touch", "system": "true"}, + {"subpath": "/input/a/click"}, + {"subpath": "/input/a/touch"}, + {"subpath": "/input/b/click"}, + {"subpath": "/input/b/touch"}, + {"subpath": "/input/squeeze/value"}, + {"subpath": "/input/squeeze/force"}, + {"subpath": "/input/trigger/click"}, + {"subpath": "/input/trigger/value"}, + {"subpath": "/input/trigger/touch"}, + {"subpath": "/input/thumbstick/x"}, + {"subpath": "/input/thumbstick/y"}, + {"subpath": "/input/thumbstick/click"}, + {"subpath": "/input/thumbstick/touch"}, + {"subpath": "/input/trackpad/x"}, + {"subpath": "/input/trackpad/y"}, + {"subpath": "/input/trackpad/force"}, + {"subpath": "/input/trackpad/touch"}, + {"subpath": "/input/grip/pose"}, + {"subpath": "/input/aim/pose"}, + {"subpath": "/output/haptic"} + ] + }, + + "/interaction_profiles/microsoft/hand_interaction": { + "title": "Microsoft hand interaction", + "extension": "XR_MSFT_hand_interaction", + "user_paths": [ + "/user/hand/left", + "/user/hand/right" + ], + "components": [ + {"subpath": "/input/select/value"}, + {"subpath": "/input/squeeze/value"}, + {"subpath": "/input/grip/pose"}, + {"subpath": "/input/aim/pose"} + ] + }, + + "/interaction_profiles/mnd/ball_on_a_stick_controller": { + "title": "Monado ball on a stick controller", + "extension": "XR_MND_ball_on_a_stick_controller", + "user_paths": [ + "/user/hand/left", + "/user/hand/right" + ], + "components": [ + {"subpath": "/input/system/click", "system": "true"}, + {"subpath": "/input/menu/click"}, + {"subpath": "/input/start/click"}, + {"subpath": "/input/select/click"}, + {"subpath": "/input/square_mnd/click"}, + {"subpath": "/input/cross_mnd/click"}, + {"subpath": "/input/circle_mnd/click"}, + {"subpath": "/input/triangle_mnd/click"}, + {"subpath": "/input/trigger/value"}, + {"subpath": "/input/grip/pose"}, + {"subpath": "/input/ball_mnd/pose"}, + {"subpath": "/input/aim/pose"}, + {"subpath": "/output/haptic"} + ] + } +} diff --git a/src/xrt/state_trackers/oxr/bindings.py b/src/xrt/state_trackers/oxr/bindings.py new file mode 100755 index 000000000..0f33961c4 --- /dev/null +++ b/src/xrt/state_trackers/oxr/bindings.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python3 +# Copyright 2020, Collabora, Ltd. +# SPDX-License-Identifier: BSL-1.0 +"""Generate code from a JSON file describing interaction profiles and bindings.""" + +import json +import argparse + + +def strip_subpath_end(path): + if (path.endswith("/value")): + return True, path[:-6] + if (path.endswith("/click")): + return True, path[:-6] + if (path.endswith("/touch")): + return True, path[:-6] + if (path.endswith("/pose")): + return True, path[:-5] + if (path.endswith("/x")): + return True, path[:-2] + if (path.endswith("/y")): + return True, path[:-2] + if (path.endswith("/output/haptic")): + return False, path + return False, path + + +class Component: + + @classmethod + def parse_array(cls, user_paths, arr): + """Turn an array of data into an array of Component objects. + Creates a Component for each user_path and stripped subpaths. + """ + check = {} + ret = [] + for elm in arr: + ups = user_paths + if ("user_paths" in elm): + ups = [elm["user_paths"]] + for up in ups: + subpath = elm["subpath"] + did_strip, stripped_path = strip_subpath_end(subpath) + fullpath = up + stripped_path + if (did_strip and not fullpath in check): + check[fullpath] = True + ret.append(cls(fullpath, elm)) + fullpath = up + subpath + ret.append(cls(fullpath, elm)) + return ret + + def __init__(self, path, elm): + """Construct an component.""" + self.path = path + + +class Profile: + """An interctive bindings profile.""" + def __init__(self, name, data): + """Construct an profile.""" + self.name = name + self.func = name[22:].replace("/", "_") + self.components = Component.parse_array(data["user_paths"], data["components"]) + self.by_length = {} + for component in self.components: + l = len(component.path) + if (l in self.by_length): + self.by_length[l].append(component) + else: + self.by_length[l] = [component] + + +class Bindings: + """A group of interactive profiles used in bindings.""" + + @classmethod + def parse(cls, data): + """Parse a dictionary defining a protocol into Profile objects.""" + return cls(data) + + @classmethod + def load_and_parse(cls, file): + """Load a JSON file and parse it into Profile objects.""" + with open(file) as infile: + return cls.parse(json.loads(infile.read())) + + def __init__(self, data): + """Construct a bindings from a dictionary of profiles.""" + self.profiles = [Profile(name, call) for name, call in data.items()] + + +header = '''// Copyright 2020, Collabora, Ltd. +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief {brief}. + * @author Jakob Bornecrantz <jakob@collabora.com> + * @ingroup {group} + */ +''' + +func_start = ''' +bool +oxr_verify_{func}_subpath(const char *str, size_t length) +{{ +\tswitch (length) {{ +''' + +if_strcmp = '''if (strcmp(str, "{check}") == 0) {{ +\t\t\treturn true; +\t\t}} else ''' + +def generate_bindings_c(file, p): + """Generate the file to verify subpaths on a interaction profile.""" + f = open(file, "w") + f.write(header.format(brief='Generated bindings data', group='oxr_main')) + f.write(''' +#include "xrt/xrt_compiler.h" + +#include <string.h> + + +// clang-format off +''') + + for profile in p.profiles: + f.write(func_start.format(func=profile.func)) + for length in profile.by_length: + f.write("\tcase " + str(length) + ":\n\t\t") + for component in profile.by_length[length]: + f.write(if_strcmp.format(check=component.path)) + f.write("{\n\t\t\treturn false;\n\t\t}\n") + f.write("\tdefault:\n\t\treturn false;\n\t}\n}\n") + f.write("\n// clang-format on\n") + f.close() + + +def generate_bindings_h(file, p): + """Generate header for the verify subpaths functions.""" + f = open(file, "w") + f.write(header.format(brief='Generated bindings data header', group='oxr_api')) + f.write(''' +#include "xrt/xrt_compiler.h" + + +// clang-format off +''') + + for profile in p.profiles: + f.write("\nbool\noxr_verify_" + profile.func + "_subpath(const char *str, size_t length);\n") + f.write("\n// clang-format on\n") + f.close() + + +def main(): + """Handle command line and generate a file.""" + parser = argparse.ArgumentParser(description='Bindings generator.') + parser.add_argument( + 'bindings', help='Bindings file to use') + parser.add_argument( + 'output', type=str, nargs='+', + help='Output file, uses the name to choose output type') + args = parser.parse_args() + + p = Bindings.load_and_parse(args.bindings) + + for output in args.output: + if output.endswith("oxr_generated_bindings.c"): + generate_bindings_c(output, p) + if output.endswith("oxr_generated_bindings.h"): + generate_bindings_h(output, p) + + +if __name__ == "__main__": + main() diff --git a/src/xrt/state_trackers/oxr/meson.build b/src/xrt/state_trackers/oxr/meson.build index 94f20952a..955c54cd5 100644 --- a/src/xrt/state_trackers/oxr/meson.build +++ b/src/xrt/state_trackers/oxr/meson.build @@ -1,6 +1,27 @@ # Copyright 2019-2020, Collabora, Ltd. # SPDX-License-Identifier: BSL-1.0 + +### +# Binding generation +# + +prog_python = import('python').find_installation('python3') + +generated = custom_target('bindings code', + command: [prog_python, '@INPUT@', '@OUTPUT@'], + input: ['bindings.py', 'bindings.json'], + output: [ + 'oxr_generated_bindings.h', + 'oxr_generated_bindings.c', + ] +) + + +### +# Main code +# + compile_args = [] if build_opengl compile_args += ['-DXR_USE_GRAPHICS_API_OPENGL', '-DXR_USE_GRAPHICS_API_OPENGL_ES'] @@ -16,7 +37,9 @@ endif lib_st_oxr = static_library( 'st_oxr', - files( + [ + generated[0], + generated[1], 'oxr_api_action.c', 'oxr_api_funcs.h', 'oxr_api_instance.c', @@ -50,7 +73,7 @@ lib_st_oxr = static_library( 'oxr_verify.c', 'oxr_vulkan.c', 'oxr_xdev.c', - ), + ], include_directories: [ xrt_include, openxr_include, diff --git a/src/xrt/state_trackers/oxr/oxr_api_action.c b/src/xrt/state_trackers/oxr/oxr_api_action.c index 027b354f7..24d7804ee 100644 --- a/src/xrt/state_trackers/oxr/oxr_api_action.c +++ b/src/xrt/state_trackers/oxr/oxr_api_action.c @@ -15,6 +15,7 @@ #include "oxr_api_funcs.h" #include "oxr_api_verify.h" +#include "oxr_generated_bindings.h" #include <stdio.h> #include <inttypes.h> @@ -105,6 +106,51 @@ oxr_xrSuggestInteractionProfileBindings( "== 0) can not suggest 0 bindings"); } + XrPath ip = suggestedBindings->interactionProfile; + const char *str = NULL; + size_t length; + + XrResult ret = oxr_path_get_string(&log, inst, ip, &str, &length); + if (ret != XR_SUCCESS) { + oxr_error( + &log, ret, + "(suggestedBindings->countSuggestedBindings == 0x%08" PRIx64 + ") invalid path", + ip); + } + + // Used in the loop that verifies the suggested bindings paths. + bool (*func)(const char *, size_t) = NULL; + + if (ip == inst->path_cache.khr_simple_controller) { + func = oxr_verify_khr_simple_controller_subpath; + } else if (ip == inst->path_cache.google_daydream_controller) { + func = oxr_verify_google_daydream_controller_subpath; + } else if (ip == inst->path_cache.htc_vive_controller) { + func = oxr_verify_htc_vive_controller_subpath; + } else if (ip == inst->path_cache.htc_vive_pro) { + func = oxr_verify_htc_vive_pro_subpath; + } else if (ip == inst->path_cache.microsoft_motion_controller) { + func = oxr_verify_microsoft_motion_controller_subpath; + } else if (ip == inst->path_cache.microsoft_xbox_controller) { + func = oxr_verify_microsoft_xbox_controller_subpath; + } else if (ip == inst->path_cache.oculus_go_controller) { + func = oxr_verify_oculus_go_controller_subpath; + } else if (ip == inst->path_cache.oculus_touch_controller) { + func = oxr_verify_oculus_touch_controller_subpath; + } else if (ip == inst->path_cache.valve_index_controller) { + func = oxr_verify_valve_index_controller_subpath; + } else if (ip == inst->path_cache.mnd_ball_on_stick_controller) { + func = oxr_verify_mnd_ball_on_a_stick_controller_subpath; + } else { + return oxr_error( + &log, XR_ERROR_PATH_UNSUPPORTED, + "(suggestedBindings->interactionProfile == \"%s\") is not " + "a supported interaction profile", + str); + } + + for (size_t i = 0; i < suggestedBindings->countSuggestedBindings; i++) { const XrActionSuggestedBinding *s = &suggestedBindings->suggestedBindings[i]; @@ -120,7 +166,9 @@ oxr_xrSuggestInteractionProfileBindings( i, act->act_set->name, act->name); } - if (!oxr_path_is_valid(&log, inst, s->binding)) { + ret = + oxr_path_get_string(&log, inst, s->binding, &str, &length); + if (ret != XR_SUCCESS) { return oxr_error( &log, XR_ERROR_PATH_INVALID, "(suggestedBindings->suggestedBindings[%zu]->" @@ -128,7 +176,13 @@ oxr_xrSuggestInteractionProfileBindings( i, s->binding); } - //! @todo verify path (s->binding). + if (!func(str, length)) { + return oxr_error( + &log, XR_ERROR_PATH_UNSUPPORTED, + "(suggestedBindings->suggestedBindings[%zu]->" + "binding == \"%s\") is not a valid path", + i, str); + } } return oxr_action_suggest_interaction_profile_bindings(