// Copyright 2018-2022, Collabora, Ltd.
// Copyright 2025-2026, NVIDIA CORPORATION.
// SPDX-License-Identifier: BSL-1.0
/*!
 * @file
 * @brief  File for verifying app input into api functions.
 * @author Rylie Pavlik <rylie.pavlik@collabora.com>
 * @author Jakob Bornecrantz <jakob@collabora.com>
 * @ingroup oxr_main
 * @ingroup oxr_api
 */

#include "xrt/xrt_compiler.h"
#include "math/m_mathinclude.h"
#include "util/u_debug.h"

#include "oxr_objects.h"
#include "oxr_logger.h"
#include "oxr_api_verify.h"
#include "oxr_chain.h"

#include "actions/oxr_subaction.h"
#include "actions/oxr_input.h"

#include <assert.h>
#include <stdio.h>
#include <string.h>


/*
 *
 * Path verification.
 *
 */

static bool
valid_path_char(const char c)
{
	if ('a' <= c && c <= 'z') {
		return true;
	}

	if ('0' <= c && c <= '9') {
		return true;
	}

	if (c == '-' || c == '_' || c == '.' || c == '/') {
		return true;
	}

	return false;
}

static bool
contains_zero(const char *path, uint32_t size)
{
	for (uint32_t i = 0; i < size; i++) {
		if (path[i] == '\0') {
			return true;
		}
	}

	return false;
}

XrResult
oxr_verify_fixed_size_single_level_path(struct oxr_logger *log, const char *path, uint32_t array_size, const char *name)
{
	if (array_size == 0) {
		return oxr_error(log, XR_ERROR_RUNTIME_FAILURE, "(%s) internal runtime error", name);
	}

	if (path[0] == '\0') {
		return oxr_error(log, XR_ERROR_NAME_INVALID, "(%s) cannot be empty", name);
	}

	if (!contains_zero(path, array_size)) {
		return oxr_error(log, XR_ERROR_PATH_FORMAT_INVALID, "(%s) must include zero termination '\\0'.", name);
	}

	size_t length = strlen(path);
	for (size_t i = 0; i < length; i++) {
		const char c = path[i];

		// Slashes are not valid in single level paths.
		if (valid_path_char(c) && c != '/') {
			continue;
		}

		return oxr_error(log, XR_ERROR_PATH_FORMAT_INVALID,
		                 "(%s) 0x%02x is not a valid character at position %u", name, c, (uint32_t)i);
	}

	return XR_SUCCESS;
}

XrResult
oxr_verify_localized_name(struct oxr_logger *log, const char *string, uint32_t array_size, const char *name)
{
	if (array_size == 0) {
		return oxr_error(log, XR_ERROR_RUNTIME_FAILURE, "(%s) internal runtime error", name);
	}

	if (string[0] == '\0') {
		return oxr_error(log, XR_ERROR_LOCALIZED_NAME_INVALID, "(%s) cannot be empty", name);
	}

	if (!contains_zero(string, array_size)) {
		return oxr_error(log, XR_ERROR_LOCALIZED_NAME_INVALID, "(%s) must include zero termination '\\0'.",
		                 name);
	}

	/// @todo validate well-formed UTF-8?
	return XR_SUCCESS;
}

enum verify_state
{
	VERIFY_START,
	VERIFY_MIDDLE,
	VERIFY_SLASH,
	VERIFY_SLASHDOTS,
};

XrResult
oxr_verify_full_path_c(struct oxr_logger *log, const char *path, const char *name)
{
	// XR_MAX_PATH_LENGTH is max including null terminator,
	// length will not include null terminator
	size_t length = XR_MAX_PATH_LENGTH;
	for (size_t i = 0; i < XR_MAX_PATH_LENGTH; i++) {
		if (path[i] == '\0') {
			length = i;
			break;
		}
	}

	return oxr_verify_full_path(log, path, (uint32_t)length, name);
}

XrResult
oxr_verify_full_path(struct oxr_logger *log, const char *path, size_t length, const char *name)
{
	enum verify_state state = VERIFY_START;
	bool valid = true;

	if (length >= XR_MAX_PATH_LENGTH) {
		char formatted_path[XR_MAX_PATH_LENGTH + 6];
		snprintf(formatted_path, XR_MAX_PATH_LENGTH + 6, "%s[...]", path);
		return oxr_error(log, XR_ERROR_PATH_FORMAT_INVALID,
		                 "(%s) is too long for a path, must be shorter "
		                 "than %u characters",
		                 name, XR_MAX_PATH_LENGTH);
	}

	for (uint32_t i = 0; i < length; i++) {
		const char c = path[i];
		switch (state) {
		case VERIFY_START:
			if (c != '/') {
				return oxr_error(log, XR_ERROR_PATH_FORMAT_INVALID,
				                 "(%s) does not start with a "
				                 "fowrward slash",
				                 name);
			}
			state = VERIFY_SLASH;
			break;
		case VERIFY_SLASH:
			switch (c) {
			case '.':
				// Is valid and starts the SlashDot(s) state.
				state = VERIFY_SLASHDOTS;
				break;
			case '/':
				return oxr_error(log, XR_ERROR_PATH_FORMAT_INVALID,
				                 "(%s) '//' is not a valid in a path", name);
			default: valid = valid_path_char(c); state = VERIFY_MIDDLE;
			}
			break;
		case VERIFY_MIDDLE:
			switch (c) {
			case '/': state = VERIFY_SLASH; break;
			default: valid = valid_path_char(c); state = VERIFY_MIDDLE;
			}
			break;
		case VERIFY_SLASHDOTS:
			switch (c) {
			case '/':
				return oxr_error(log, XR_ERROR_PATH_FORMAT_INVALID,
				                 "(%s) '/.[.]*/' is not a valid in a path", name);
			case '.':
				// It's valid, more ShashDot(s).
				break;
			default: valid = valid_path_char(c); state = VERIFY_MIDDLE;
			}
			break;
		}

		if (valid) {
			// Can't end with slash
			valid = (path[length - 1] != '/');
		}
		if (!valid) {
			return oxr_error(log, XR_ERROR_PATH_FORMAT_INVALID,
			                 "(%s) 0x%02x is not a valid character "
			                 "at position %u",
			                 name, c, (uint32_t)length);
		}
	}

	switch (state) {
	case VERIFY_START:
		// Empty string
		return oxr_error(log, XR_ERROR_PATH_FORMAT_INVALID, "(%s) a empty string is not a valid path", name);
	case VERIFY_SLASH:
		// Is this '/path_component/' or '/'
		if (length > 1) {
			// It was '/path_component/'
			return XR_SUCCESS;
		}
		// It was '/'
		return oxr_error(log, XR_ERROR_PATH_FORMAT_INVALID, "(%s) the string '%s' is not a valid path", name,
		                 path);
	case VERIFY_SLASHDOTS:
		// Does the path ends with '/..'
		return oxr_error(log, XR_ERROR_PATH_FORMAT_INVALID, "(%s) strings ending with '/.[.]*' is not a valid",
		                 name);

	case VERIFY_MIDDLE:
		// '/path_component/trailing_path_component' okay!
		return XR_SUCCESS;
	default:
		// We should not end up here.
		return oxr_error(log, XR_ERROR_RUNTIME_FAILURE, "(%s) internal runtime error validating path (%s)",
		                 name, path);
	}
}


/*
 *
 * Subaction path functions.
 *
 */

static XrResult
subaction_path_no_dups(struct oxr_logger *log,
                       struct oxr_instance *inst,
                       struct oxr_subaction_paths *subaction_paths,
                       XrPath path,
                       const char *variable,
                       uint32_t index)
{
	assert(subaction_paths);
	bool duplicate = false;

	if (path == XR_NULL_PATH) {
		return oxr_error(log, XR_ERROR_PATH_INVALID,
		                 "(%s[%u] == XR_NULL_PATH) not a "
		                 "valid subaction path.",
		                 variable, index);
	}

#define HANDLE_SUBACTION_PATH(X)                                                                                       \
	if (path == inst->path_cache.X) {                                                                              \
		if (subaction_paths->X) {                                                                              \
			duplicate = true;                                                                              \
		} else {                                                                                               \
			subaction_paths->X = true;                                                                     \
		}                                                                                                      \
	} else

	OXR_FOR_EACH_VALID_SUBACTION_PATH(HANDLE_SUBACTION_PATH)
	{
		// else clause
		const char *str = NULL;
		size_t length = 0;

		oxr_path_get_string(log, inst, path, &str, &length);
		return oxr_error(log, XR_ERROR_PATH_UNSUPPORTED,
		                 "(%s[%u] == '%s') path is not a "
		                 "valid subaction path.",
		                 variable, index, str);
	}
#undef HANDLE_SUBACTION_PATH

	if (duplicate) {
		const char *str = NULL;
		size_t length = 0;

		oxr_path_get_string(log, inst, path, &str, &length);

		return oxr_error(log, XR_ERROR_PATH_UNSUPPORTED, "(%s[%u] == '%s') duplicate paths", variable, index,
		                 str);
	}

	return XR_SUCCESS;
}


XrResult
oxr_verify_subaction_paths_create(struct oxr_logger *log,
                                  struct oxr_instance *inst,
                                  uint32_t countSubactionPaths,
                                  const XrPath *subactionPaths,
                                  const char *variable)
{
	struct oxr_subaction_paths subaction_paths = {0};

	for (uint32_t i = 0; i < countSubactionPaths; i++) {
		XrPath path = subactionPaths[i];

		XrResult ret = subaction_path_no_dups(log, inst, &subaction_paths, path, variable, i);
		if (ret != XR_SUCCESS) {
			return ret;
		}
	}

	return XR_SUCCESS;
}

XrResult
oxr_verify_subaction_path_sync(struct oxr_logger *log,
                               const struct oxr_instance *inst,
                               const struct oxr_action_set *act_set,
                               XrPath path,
                               uint32_t index)
{
	if (path == XR_NULL_PATH) {
		return XR_SUCCESS;
	}
	struct oxr_subaction_paths subaction_paths = {0};
	if (!oxr_classify_subaction_path(&inst->path_cache, path, &subaction_paths)) {
		const char *str = NULL;
		size_t length = 0;

		oxr_path_get_string(log, inst, path, &str, &length);
		return oxr_error(log, XR_ERROR_PATH_INVALID,
		                 "(actionSets[%i].subactionPath == '%s') path "
		                 "is not a valid subaction path.",
		                 index, str);
	}
	if (!oxr_subaction_paths_is_subset_of(&subaction_paths, &(act_set->data->permitted_subaction_paths))) {
		return oxr_error(log, XR_ERROR_PATH_UNSUPPORTED,
		                 "(actionSets[%i].subactionPath) action set '%s' has no "
		                 "actions defined with the specified subaction path",
		                 index, act_set != NULL ? act_set->data->name : "NULL");
	}
	return XR_SUCCESS;
}

XrResult
oxr_verify_subaction_path_get(struct oxr_logger *log,
                              struct oxr_instance *inst,
                              XrPath path,
                              const struct oxr_subaction_paths *act_subaction_paths,
                              struct oxr_subaction_paths *out_subaction_paths,
                              const char *variable)
{
	struct oxr_subaction_paths subaction_paths = {0};

#define GET_PATH(X)                                                                                                    \
	else if (path == inst->path_cache.X)                                                                           \
	{                                                                                                              \
		subaction_paths.X = true;                                                                              \
	}

	if (path == XR_NULL_PATH) {
		subaction_paths.any = true;
	}
	OXR_FOR_EACH_SUBACTION_PATH(GET_PATH) else
	{
		const char *str = NULL;
		size_t length = 0;

		oxr_path_get_string(log, inst, path, &str, &length);
		return oxr_error(log, XR_ERROR_PATH_INVALID,
		                 "(%s == '%s') path is not "
		                 "a valid subaction path.",
		                 variable, str);
	}
#undef GET_PATH

	bool fail = false;
#define CHECK_CREATION_TIME(X)                                                                                         \
	if (subaction_paths.X && !act_subaction_paths->X) {                                                            \
		fail = true;                                                                                           \
	}

	OXR_FOR_EACH_SUBACTION_PATH(CHECK_CREATION_TIME);
#undef CHECK_CREATION_TIME

	if (fail) {
		const char *str = NULL;
		size_t length = 0;

		oxr_path_get_string(log, inst, path, &str, &length);

		return oxr_error(log, XR_ERROR_PATH_UNSUPPORTED,
		                 "(%s == '%s') the subaction path was "
		                 "not specified at action creation",
		                 variable, str);
	}

	*out_subaction_paths = subaction_paths;

	return XR_SUCCESS;
}


/*
 *
 * Other verification.
 *
 */

XrResult
oxr_verify_extensions(struct oxr_logger *log, const struct oxr_extension_status *extensions)
{
#ifdef XR_USE_GRAPHICS_API_VULKAN
	if ((extensions->KHR_swapchain_usage_input_attachment_bit ||
	     extensions->MND_swapchain_usage_input_attachment_bit) &&
	    !extensions->KHR_vulkan_enable2 && !extensions->KHR_vulkan_enable) {
		return oxr_error(log, XR_ERROR_VALIDATION_FAILURE,
		                 "Using [KHR|MND]_swapchain_usage_input_attachment_bit doesn't make sense without "
		                 "KHR_vulkan_enable[2].");
	}
#endif

#ifdef OXR_HAVE_EXT_dpad_binding
	if (extensions->EXT_dpad_binding && !extensions->KHR_binding_modification) {
		return oxr_error(log, XR_ERROR_VALIDATION_FAILURE,
		                 "XR_EXT_dpad_binding requires XR_KHR_binding_modification");
	}
#endif

	return XR_SUCCESS;
}

XrResult
oxr_verify_action_sets_array(struct oxr_logger *log,
                             uint32_t countActionSets,
                             const XrActionSet *actionSets,
                             const char *variable_name)
{
	if (countActionSets == 0 || actionSets == NULL) {
		return oxr_error(log, XR_ERROR_VALIDATION_FAILURE,
		                 "(%s) countActionSets must be > 0 and actionSets must be non-NULL", variable_name);
	}

	for (uint32_t i = 0; i < countActionSets; i++) {
		if (actionSets[i] == XR_NULL_HANDLE) {
			return oxr_error(log, XR_ERROR_HANDLE_INVALID, "(%s[%u]) is XR_NULL_HANDLE", variable_name, i);
		}
		struct oxr_action_set *act_set = XRT_CAST_OXR_HANDLE_TO_PTR(struct oxr_action_set *, actionSets[i]);
		if (act_set->handle.debug != OXR_XR_DEBUG_ACTIONSET) {
			return oxr_error(log, XR_ERROR_HANDLE_INVALID, "(%s[%u]) is not a valid XrActionSet",
			                 variable_name, i);
		}
		if (act_set->handle.state != OXR_HANDLE_STATE_LIVE) {
			return oxr_error(log, XR_ERROR_HANDLE_INVALID, "(%s[%u]) is not live", variable_name, i);
		}
	}

	return XR_SUCCESS;
}

XrResult
oxr_verify_active_action_sets_sync(struct oxr_logger *log,
                                   const struct oxr_instance *inst,
                                   uint32_t countActiveActionSets,
                                   const XrActiveActionSet *activeActionSets,
                                   const char *variable_name)
{
	for (uint32_t i = 0; i < countActiveActionSets; i++) {
		if (activeActionSets[i].actionSet == XR_NULL_HANDLE) {
			return oxr_error(log, XR_ERROR_HANDLE_INVALID, "(%s[%u].actionSet) is XR_NULL_HANDLE",
			                 variable_name, i);
		}
		struct oxr_action_set *act_set =
		    XRT_CAST_OXR_HANDLE_TO_PTR(struct oxr_action_set *, activeActionSets[i].actionSet);
		if (act_set->handle.debug != OXR_XR_DEBUG_ACTIONSET) {
			return oxr_error(log, XR_ERROR_HANDLE_INVALID, "(%s[%u].actionSet) is not a valid XrActionSet",
			                 variable_name, i);
		}
		if (act_set->handle.state != OXR_HANDLE_STATE_LIVE) {
			return oxr_error(log, XR_ERROR_HANDLE_INVALID, "(%s[%u].actionSet) is not live", variable_name,
			                 i);
		}

		XrResult res = oxr_verify_subaction_path_sync(log, inst, act_set, activeActionSets[i].subactionPath, i);
		if (res != XR_SUCCESS) {
			return res;
		}
	}

	return XR_SUCCESS;
}

XrResult
oxr_verify_view_config_type(struct oxr_logger *log,
                            struct oxr_instance *inst,
                            XrViewConfigurationType view_conf,
                            const char *view_conf_name)
{
	// These are always valid.
	if (view_conf == XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MONO ||
	    view_conf == XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO) {
		return XR_SUCCESS;
	}

	// Valid in OpenXR 1.1 and forward.
	if (OXR_API_VERSION_AT_LEAST(inst, 1, 1)) {
		if (view_conf == XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO_WITH_FOVEATED_INSET) {
			return XR_SUCCESS;
		}
	}

	return oxr_error(log, XR_ERROR_VALIDATION_FAILURE, "(%s == 0x%08x) invalid view configuration type",
	                 view_conf_name, view_conf);
}

XrResult
oxr_verify_view_config_type_supported(struct oxr_logger *log,
                                      struct oxr_system *sys,
                                      XrViewConfigurationType view_conf,
                                      const char *view_conf_name)
{
	for (uint32_t i = 0; i < sys->view_config_count; i++) {
		struct oxr_view_config_properties *props = &sys->view_configs[i];
		if (props->view_config_type == view_conf) {
			return XR_SUCCESS;
		}
	}

	return oxr_error(log, XR_ERROR_VIEW_CONFIGURATION_TYPE_UNSUPPORTED,
	                 "(%s == 0x%08x) unsupported view configuration type by system %" PRIu64, view_conf_name,
	                 view_conf, sys->systemId);
}

XrResult
oxr_verify_XrSessionCreateInfo(struct oxr_logger *log,
                               const struct oxr_instance *inst,
                               const XrSessionCreateInfo *createInfo)
{
	if (createInfo->type != XR_TYPE_SESSION_CREATE_INFO) {
		return oxr_error(log, XR_ERROR_VALIDATION_FAILURE, "(createInfo->type)");
	}

	if (createInfo->createFlags != 0) {
		return oxr_error(log, XR_ERROR_VALIDATION_FAILURE, "Non-zero session create flags");
	}

	XrResult result = oxr_system_verify_id(log, inst, createInfo->systemId);
	if (result != XR_SUCCESS) {
		return result;
	}

#if defined(OXR_HAVE_KHR_opengl_enable) && defined(XR_USE_PLATFORM_XLIB)
	XrGraphicsBindingOpenGLXlibKHR const *opengl_xlib = OXR_GET_INPUT_FROM_CHAIN(
	    createInfo, XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR, XrGraphicsBindingOpenGLXlibKHR);
	if (opengl_xlib != NULL) {
		OXR_VERIFY_EXTENSION(log, inst, KHR_opengl_enable);
		return oxr_verify_XrGraphicsBindingOpenGLXlibKHR(log, opengl_xlib);
	}
#endif // defined(OXR_HAVE_KHR_opengl_enable) && defined(XR_USE_PLATFORM_XLIB)

#if defined(OXR_HAVE_KHR_opengl_enable) && defined(XR_USE_PLATFORM_WIN32)
	XrGraphicsBindingOpenGLWin32KHR const *opengl_win32 = OXR_GET_INPUT_FROM_CHAIN(
	    createInfo, XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR, XrGraphicsBindingOpenGLWin32KHR);
	if (opengl_win32 != NULL) {
		OXR_VERIFY_EXTENSION(log, inst, KHR_opengl_enable);
		return oxr_verify_XrGraphicsBindingOpenGLWin32KHR(log, opengl_win32);
	}
#endif // defined(OXR_HAVE_KHR_opengl_enable) && defined(XR_USE_PLATFORM_WIN32)

#if defined(OXR_HAVE_KHR_vulkan_enable) || defined(OXR_HAVE_KHR_vulkan_enable2)
	/* XR_TYPE_GRAPHICS_BINDING_VULKAN2_KHR aliased to
	 * XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR */
	XrGraphicsBindingVulkanKHR const *vulkan =
	    OXR_GET_INPUT_FROM_CHAIN(createInfo, XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR, XrGraphicsBindingVulkanKHR);
	if (vulkan != NULL) {
		OXR_VERIFY_EXTENSIONS_OR(log, inst, KHR_vulkan_enable, KHR_vulkan_enable2);
		return oxr_verify_XrGraphicsBindingVulkanKHR(log, vulkan);
	}
#endif // defined(OXR_HAVE_KHR_vulkan_enable) ||
       // defined(OXR_HAVE_KHR_vulkan_enable2)

#if defined(OXR_HAVE_MNDX_egl_enable) && defined(XR_USE_PLATFORM_EGL)
	XrGraphicsBindingEGLMNDX const *egl =
	    OXR_GET_INPUT_FROM_CHAIN(createInfo, XR_TYPE_GRAPHICS_BINDING_EGL_MNDX, XrGraphicsBindingEGLMNDX);
	if (egl != NULL) {
		OXR_VERIFY_EXTENSION(log, inst, MNDX_egl_enable);
		return oxr_verify_XrGraphicsBindingEGLMNDX(log, egl);
	}
#endif // defined(OXR_HAVE_MNDX_egl_enable) && defined(XR_USE_PLATFORM_EGL_KHR)

#if defined(XR_USE_PLATFORM_ANDROID) && defined(XR_USE_GRAPHICS_API_OPENGL_ES)
	XrGraphicsBindingOpenGLESAndroidKHR const *opengles_android = OXR_GET_INPUT_FROM_CHAIN(
	    createInfo, XR_TYPE_GRAPHICS_BINDING_OPENGL_ES_ANDROID_KHR, XrGraphicsBindingOpenGLESAndroidKHR);
	if (opengles_android != NULL) {
		OXR_VERIFY_EXTENSION(log, inst, KHR_opengl_es_enable);
		return oxr_verify_XrGraphicsBindingOpenGLESAndroidKHR(log, opengles_android);
	}
#endif // defined(XR_USE_PLATFORM_ANDROID) && defined(XR_USE_GRAPHICS_API_OPENGL_ES)

#if defined(XR_USE_GRAPHICS_API_D3D11)
	XrGraphicsBindingD3D11KHR const *d3d11 =
	    OXR_GET_INPUT_FROM_CHAIN(createInfo, XR_TYPE_GRAPHICS_BINDING_D3D11_KHR, XrGraphicsBindingD3D11KHR);
	if (d3d11 != NULL) {
		OXR_VERIFY_EXTENSION(log, inst, KHR_D3D11_enable);
		return oxr_verify_XrGraphicsBindingD3D11KHR(log, d3d11);
	}
#endif // XR_USE_GRAPHICS_API_D3D11


#if defined(XR_USE_GRAPHICS_API_D3D12)
	XrGraphicsBindingD3D12KHR const *d3d12 =
	    OXR_GET_INPUT_FROM_CHAIN(createInfo, XR_TYPE_GRAPHICS_BINDING_D3D12_KHR, XrGraphicsBindingD3D12KHR);
	if (d3d12 != NULL) {
		OXR_VERIFY_EXTENSION(log, inst, KHR_D3D12_enable);
		return oxr_verify_XrGraphicsBindingD3D12KHR(log, d3d12);
	}
#endif // XR_USE_GRAPHICS_API_D3D12

	/*
	 * Add any new graphics binding structs here - before the headless
	 * check. (order for non-headless checks not specified in standard.)
	 * Add a new verify function below.
	 * Any new addition will also need to be added to
	 * oxr_session_create_impl.
	 */

	/* We didn't recognize any graphics binding structs in the chain - our
	 * last hope is headless. */

#ifdef OXR_HAVE_MND_headless
	if (inst->extensions.MND_headless) {
		return XR_SUCCESS;
	}
#endif // OXR_HAVE_MND_headless

	return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID,
	                 "(createInfo->next) Argument chain does not contain "
	                 "any known graphics bindings");
}


#if defined(XR_USE_PLATFORM_XLIB) && defined(XR_USE_GRAPHICS_API_OPENGL)

XrResult
oxr_verify_XrGraphicsBindingOpenGLXlibKHR(struct oxr_logger *log, const XrGraphicsBindingOpenGLXlibKHR *next)
{
	if (next->type != XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID, "Graphics binding has invalid type");
	}

	if (next->xDisplay == NULL) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID,
		                 "XrGraphicsBindingOpenGLXlibKHR::xDisplay is NULL");
	}

	if (next->glxContext == NULL) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID,
		                 "XrGraphicsBindingOpenGLXlibKHR::glxContext is NULL");
	}

	if (next->glxDrawable == NULL) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID,
		                 "XrGraphicsBindingOpenGLXlibKHR::glxDrawable is NULL");
	}

	if (next->glxFBConfig == NULL) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID,
		                 "XrGraphicsBindingOpenGLXlibKHR::glxFBConfig is NULL");
	}

	if (next->visualid == 0) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID,
		                 "XrGraphicsBindingOpenGLXlibKHR::visualid is 0");
	}

	return XR_SUCCESS;
}

#endif // defined(XR_USE_PLATFORM_XLIB) && defined(XR_USE_GRAPHICS_API_OPENGL)

#if defined(XR_USE_PLATFORM_WIN32) && defined(XR_USE_GRAPHICS_API_OPENGL)

XrResult
oxr_verify_XrGraphicsBindingOpenGLWin32KHR(struct oxr_logger *log, const XrGraphicsBindingOpenGLWin32KHR *next)
{
	if (next->type != XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID, "Graphics binding has invalid type");
	}

	if (next->hDC == NULL) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID, "hDC is NULL");
	}

	if (next->hGLRC == NULL) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID, "hGLRC is NULL");
	}

	return XR_SUCCESS;
}

#endif // defined(XR_USE_PLATFORM_WIN32) && defined(XR_USE_GRAPHICS_API_OPENGL)


#ifdef XR_USE_GRAPHICS_API_VULKAN

XrResult
oxr_verify_XrGraphicsBindingVulkanKHR(struct oxr_logger *log, const XrGraphicsBindingVulkanKHR *next)
{
	/* XR_TYPE_GRAPHICS_BINDING_VULKAN2_KHR aliased to
	 * XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR */
	if (next->type != XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID, "Graphics binding has invalid type");
	}

	if (next->instance == VK_NULL_HANDLE) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID,
		                 "XrGraphicsBindingVulkanKHR(2)::instance is invalid");
	}

	if (next->physicalDevice == VK_NULL_HANDLE) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID,
		                 "XrGraphicsBindingVulkanKHR(2)::physicalDevice is invalid");
	}

	if (next->device == VK_NULL_HANDLE) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID,
		                 "XrGraphicsBindingVulkanKHR(2)::device is invalid");
	}

	if (next->queueFamilyIndex == VK_QUEUE_FAMILY_IGNORED) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID,
		                 "XrGraphicsBindingVulkanKHR(2)::queueFamilyIndex is invalid");
	}

	if (next->queueIndex == UINT32_MAX) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID,
		                 "XrGraphicsBindingVulkanKHR(2)::queueIndex is invalid");
	}

	return XR_SUCCESS;
}

#endif // XR_USE_GRAPHICS_API_VULKAN


#ifdef XR_USE_PLATFORM_EGL

XrResult
oxr_verify_XrGraphicsBindingEGLMNDX(struct oxr_logger *log, const XrGraphicsBindingEGLMNDX *next)
{
	// Here for internal error checking
	if (next->type != XR_TYPE_GRAPHICS_BINDING_EGL_MNDX) {
		return oxr_error(log, XR_ERROR_RUNTIME_FAILURE, "XrGraphicsBindingEGLMNDX::type is invalid '%i'",
		                 next->type);
	}

	if (next->getProcAddress == NULL) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID,
		                 "XrGraphicsBindingEGLMNDX::getProcAddress cannot be NULL");
	}

	if (next->display == NULL) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID,
		                 "XrGraphicsBindingEGLMNDX::display cannot be NULL");
	}

	// The next->config field can be NULL if EGL_KHR_no_config_context is supported by the display.

	if (next->context == NULL) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID,
		                 "XrGraphicsBindingEGLMNDX::context cannot be NULL");
	}

	return XR_SUCCESS;
}

#endif

#if defined(XR_USE_PLATFORM_ANDROID) && defined(XR_USE_GRAPHICS_API_OPENGL_ES)
XrResult
oxr_verify_XrGraphicsBindingOpenGLESAndroidKHR(struct oxr_logger *log, const XrGraphicsBindingOpenGLESAndroidKHR *next)
{
	if (next->type != XR_TYPE_GRAPHICS_BINDING_OPENGL_ES_ANDROID_KHR) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID, "Graphics binding has invalid type");
	}

	if (next->display == NULL) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID,
		                 "XrGraphicsBindingOpenGLESAndroidKHR::display cannot be NULL");
	}

	// The next->config field can be NULL if EGL_KHR_no_config_context is supported by the display.

	if (next->context == NULL) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID,
		                 "XrGraphicsBindingOpenGLESAndroidKHR::context cannot be NULL");
	}

	return XR_SUCCESS;
}
#endif // defined(XR_USE_PLATFORM_ANDROID) &&
       // defined(XR_USE_GRAPHICS_API_OPENGL_ES)

#if defined(XR_USE_GRAPHICS_API_D3D11)
XrResult
oxr_verify_XrGraphicsBindingD3D11KHR(struct oxr_logger *log, const XrGraphicsBindingD3D11KHR *next)
{
	if (next->type != XR_TYPE_GRAPHICS_BINDING_D3D11_KHR) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID, "Graphics binding has invalid type");
	}
	if (next->device == NULL) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID,
		                 "XrGraphicsBindingD3D11KHR::device cannot be NULL");
	}
	return XR_SUCCESS;
}
#endif // defined(XR_USE_GRAPHICS_API_D3D11)

#if defined(XR_USE_GRAPHICS_API_D3D12)
XrResult
oxr_verify_XrGraphicsBindingD3D12KHR(struct oxr_logger *log, const XrGraphicsBindingD3D12KHR *next)
{
	if (next->type != XR_TYPE_GRAPHICS_BINDING_D3D12_KHR) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID, "Graphics binding has invalid type");
	}
	if (next->device == NULL) {
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID,
		                 "XrGraphicsBindingD3D12KHR::device cannot be NULL");
	}
	if (next->queue == NULL) {
		// not specified in spec nor cts, so assume it's this
		return oxr_error(log, XR_ERROR_GRAPHICS_DEVICE_INVALID,
		                 "XrGraphicsBindingD3D12KHR::queue cannot be NULL");
	}
	return XR_SUCCESS;
}
#endif // defined(XR_USE_GRAPHICS_API_D3D12)

#ifdef XR_EXT_dpad_binding
XrResult
oxr_verify_XrInteractionProfileDpadBindingEXT(struct oxr_logger *log,
                                              const XrInteractionProfileDpadBindingEXT *dpad,
                                              const char *error_prefix)
{
	if (dpad->forceThreshold <= 0.0 || dpad->forceThreshold > 1.0) {
		return oxr_error(log, XR_ERROR_VALIDATION_FAILURE,
		                 "(%s->forceThreshold == %f) is not within range of (0.0 .. 1.0].", error_prefix,
		                 dpad->forceThreshold);
	}

	if (dpad->forceThresholdReleased <= 0.0 || dpad->forceThresholdReleased > 1.0) {
		return oxr_error(log, XR_ERROR_VALIDATION_FAILURE,
		                 "(%s->forceThresholdReleased == %f) is not within range of (0.0 .. 1.0].",
		                 error_prefix, dpad->forceThresholdReleased);
	}

	if (dpad->forceThreshold < dpad->forceThresholdReleased) {
		return oxr_error(log, XR_ERROR_VALIDATION_FAILURE,
		                 "(%s->[forceThreshold '%f' < forceThresholdReleased '%f']) forceThreshold is not "
		                 "equal or greater than forceThresholdReleased.",
		                 error_prefix, dpad->forceThreshold, dpad->forceThresholdReleased);
	}

	if (dpad->centerRegion <= 0.0 || dpad->centerRegion >= 1.0) {
		return oxr_error(log, XR_ERROR_VALIDATION_FAILURE,
		                 "(%s->centerRegion == %f) is outside the exclusive range (0.0 .. 1.0).", error_prefix,
		                 dpad->centerRegion);
	}

	if (dpad->wedgeAngle < 0 || dpad->wedgeAngle >= M_PI) {
		return oxr_error(log, XR_ERROR_VALIDATION_FAILURE,
		                 "(%s->wedgeAngle == %f) is outside the half-open range [0.0 .. π).", error_prefix,
		                 dpad->wedgeAngle);
	}

	return XR_SUCCESS;
}
#endif // XR_EXT_dpad_binding

#ifdef OXR_HAVE_EXT_hand_tracking_data_source
XrResult
oxr_verify_XrHandTrackingDataSourceInfoEXT(struct oxr_logger *log,
                                           const XrHandTrackingDataSourceInfoEXT *data_source_info)
{
	OXR_VERIFY_ARG_TYPE_AND_NOT_NULL(log, data_source_info, XR_TYPE_HAND_TRACKING_DATA_SOURCE_INFO_EXT);
	OXR_VERIFY_ARG_NOT_ZERO(log, data_source_info->requestedDataSourceCount);
	OXR_VERIFY_ARG_NOT_NULL(log, data_source_info->requestedDataSources);
	// verify unique / no duplicate entries in requestedDataSources
	for (uint32_t i = 0; i < data_source_info->requestedDataSourceCount; ++i) {
		for (uint32_t j = i + 1; j < data_source_info->requestedDataSourceCount; ++j) {
			if (data_source_info->requestedDataSources[j] != data_source_info->requestedDataSources[i]) {
				continue;
			}
			return oxr_error(log, XR_ERROR_VALIDATION_FAILURE,
			                 "Duplicate value in requestedDataSources at indices: %u, %u\n", i, j);
		}
	}
	return XR_SUCCESS;
}
#endif // XR_EXT_hand_tracking_data_source
