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(