st/oxr: Improve xrSuggestInteractionProfileBindings validation of input

This commit is contained in:
Jakob Bornecrantz 2020-06-01 09:54:15 +01:00
parent cdde7cd2c2
commit 2dcf4a819b
6 changed files with 539 additions and 5 deletions

View file

@ -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.

View file

@ -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}
)

View file

@ -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"}
]
}
}

View file

@ -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()

View file

@ -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,

View file

@ -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(