/*
 * windows UsbDk backend for libusb 1.0
 * Copyright © 2014 Red Hat, Inc.

 * Authors:
 * Dmitry Fleytman <dmitry@daynix.com>
 * Pavel Gurvich <pavel@daynix.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <config.h>

#if defined(USE_USBDK)

#include <windows.h>
#include <cfgmgr32.h>
#include <stdio.h>

#include "libusbi.h"
#include "windows_common.h"
#include "windows_nt_common.h"

#define ULONG64 uint64_t
#define PVOID64 uint64_t

typedef CONST WCHAR *PCWCHAR;
#define wcsncpy_s wcsncpy

#include "windows_usbdk.h"

#if !defined(STATUS_SUCCESS)
typedef LONG NTSTATUS;
#define STATUS_SUCCESS			((NTSTATUS)0x00000000L)
#endif

#if !defined(STATUS_CANCELLED)
#define STATUS_CANCELLED		((NTSTATUS)0xC0000120L)
#endif

#if !defined(STATUS_REQUEST_CANCELED)
#define STATUS_REQUEST_CANCELED		((NTSTATUS)0xC0000703L)
#endif

#if !defined(USBD_SUCCESS)
typedef int32_t USBD_STATUS;
#define USBD_SUCCESS(Status)		((USBD_STATUS) (Status) >= 0)
#define USBD_PENDING(Status)		((ULONG) (Status) >> 30 == 1)
#define USBD_ERROR(Status)		((USBD_STATUS) (Status) < 0)
#define USBD_STATUS_STALL_PID		((USBD_STATUS) 0xc0000004)
#define USBD_STATUS_ENDPOINT_HALTED	((USBD_STATUS) 0xc0000030)
#define USBD_STATUS_BAD_START_FRAME	((USBD_STATUS) 0xc0000a00)
#define USBD_STATUS_TIMEOUT		((USBD_STATUS) 0xc0006000)
#define USBD_STATUS_CANCELED		((USBD_STATUS) 0xc0010000)
#endif

static int concurrent_usage = -1;

struct usbdk_device_priv {
	USB_DK_DEVICE_INFO info;
	PUSB_CONFIGURATION_DESCRIPTOR *config_descriptors;
	HANDLE redirector_handle;
	uint8_t active_configuration;
};

struct usbdk_transfer_priv {
	USB_DK_TRANSFER_REQUEST request;
	struct winfd pollable_fd;
	PULONG64 IsochronousPacketsArray;
	PUSB_DK_ISO_TRANSFER_RESULT IsochronousResultsArray;
};

static inline struct usbdk_device_priv *_usbdk_device_priv(struct libusb_device *dev)
{
	return (struct usbdk_device_priv *)dev->os_priv;
}

static inline struct usbdk_transfer_priv *_usbdk_transfer_priv(struct usbi_transfer *itransfer)
{
	return (struct usbdk_transfer_priv *)usbi_transfer_get_os_priv(itransfer);
}

static struct {
	HMODULE module;

	USBDK_GET_DEVICES_LIST			GetDevicesList;
	USBDK_RELEASE_DEVICES_LIST		ReleaseDevicesList;
	USBDK_START_REDIRECT			StartRedirect;
	USBDK_STOP_REDIRECT			StopRedirect;
	USBDK_GET_CONFIGURATION_DESCRIPTOR	GetConfigurationDescriptor;
	USBDK_RELEASE_CONFIGURATION_DESCRIPTOR	ReleaseConfigurationDescriptor;
	USBDK_READ_PIPE				ReadPipe;
	USBDK_WRITE_PIPE			WritePipe;
	USBDK_ABORT_PIPE			AbortPipe;
	USBDK_RESET_PIPE			ResetPipe;
	USBDK_SET_ALTSETTING			SetAltsetting;
	USBDK_RESET_DEVICE			ResetDevice;
	USBDK_GET_REDIRECTOR_SYSTEM_HANDLE	GetRedirectorSystemHandle;
} usbdk_helper;

static FARPROC get_usbdk_proc_addr(struct libusb_context *ctx, LPCSTR api_name)
{
	FARPROC api_ptr = GetProcAddress(usbdk_helper.module, api_name);

	if (api_ptr == NULL)
		usbi_err(ctx, "UsbDkHelper API %s not found, error %d", api_name, GetLastError());

	return api_ptr;
}

static void unload_usbdk_helper_dll(void)
{
	if (usbdk_helper.module != NULL) {
		FreeLibrary(usbdk_helper.module);
		usbdk_helper.module = NULL;
	}
}

static int load_usbdk_helper_dll(struct libusb_context *ctx)
{
	usbdk_helper.module = LoadLibraryA("UsbDkHelper");
	if (usbdk_helper.module == NULL) {
		usbi_err(ctx, "Failed to load UsbDkHelper.dll, error %d", GetLastError());
		return LIBUSB_ERROR_NOT_FOUND;
	}

	usbdk_helper.GetDevicesList = (USBDK_GET_DEVICES_LIST)get_usbdk_proc_addr(ctx, "UsbDk_GetDevicesList");
	if (usbdk_helper.GetDevicesList == NULL)
		goto error_unload;

	usbdk_helper.ReleaseDevicesList = (USBDK_RELEASE_DEVICES_LIST)get_usbdk_proc_addr(ctx, "UsbDk_ReleaseDevicesList");
	if (usbdk_helper.ReleaseDevicesList == NULL)
		goto error_unload;

	usbdk_helper.StartRedirect = (USBDK_START_REDIRECT)get_usbdk_proc_addr(ctx, "UsbDk_StartRedirect");
	if (usbdk_helper.StartRedirect == NULL)
		goto error_unload;

	usbdk_helper.StopRedirect = (USBDK_STOP_REDIRECT)get_usbdk_proc_addr(ctx, "UsbDk_StopRedirect");
	if (usbdk_helper.StopRedirect == NULL)
		goto error_unload;

	usbdk_helper.GetConfigurationDescriptor = (USBDK_GET_CONFIGURATION_DESCRIPTOR)get_usbdk_proc_addr(ctx, "UsbDk_GetConfigurationDescriptor");
	if (usbdk_helper.GetConfigurationDescriptor == NULL)
		goto error_unload;

	usbdk_helper.ReleaseConfigurationDescriptor = (USBDK_RELEASE_CONFIGURATION_DESCRIPTOR)get_usbdk_proc_addr(ctx, "UsbDk_ReleaseConfigurationDescriptor");
	if (usbdk_helper.ReleaseConfigurationDescriptor == NULL)
		goto error_unload;

	usbdk_helper.ReadPipe = (USBDK_READ_PIPE)get_usbdk_proc_addr(ctx, "UsbDk_ReadPipe");
	if (usbdk_helper.ReadPipe == NULL)
		goto error_unload;

	usbdk_helper.WritePipe = (USBDK_WRITE_PIPE)get_usbdk_proc_addr(ctx, "UsbDk_WritePipe");
	if (usbdk_helper.WritePipe == NULL)
		goto error_unload;

	usbdk_helper.AbortPipe = (USBDK_ABORT_PIPE)get_usbdk_proc_addr(ctx, "UsbDk_AbortPipe");
	if (usbdk_helper.AbortPipe == NULL)
		goto error_unload;

	usbdk_helper.ResetPipe = (USBDK_RESET_PIPE)get_usbdk_proc_addr(ctx, "UsbDk_ResetPipe");
	if (usbdk_helper.ResetPipe == NULL)
		goto error_unload;

	usbdk_helper.SetAltsetting = (USBDK_SET_ALTSETTING)get_usbdk_proc_addr(ctx, "UsbDk_SetAltsetting");
	if (usbdk_helper.SetAltsetting == NULL)
		goto error_unload;

	usbdk_helper.ResetDevice = (USBDK_RESET_DEVICE)get_usbdk_proc_addr(ctx, "UsbDk_ResetDevice");
	if (usbdk_helper.ResetDevice == NULL)
		goto error_unload;

	usbdk_helper.GetRedirectorSystemHandle = (USBDK_GET_REDIRECTOR_SYSTEM_HANDLE)get_usbdk_proc_addr(ctx, "UsbDk_GetRedirectorSystemHandle");
	if (usbdk_helper.GetRedirectorSystemHandle == NULL)
		goto error_unload;

	return LIBUSB_SUCCESS;

error_unload:
	FreeLibrary(usbdk_helper.module);
	usbdk_helper.module = NULL;
	return LIBUSB_ERROR_NOT_FOUND;
}

static int usbdk_init(struct libusb_context *ctx)
{
	int r;

	if (++concurrent_usage == 0) { // First init?
		r = load_usbdk_helper_dll(ctx);
		if (r)
			goto init_exit;

		init_polling();

		r = windows_common_init(ctx);
		if (r)
			goto init_exit;
	}
	// At this stage, either we went through full init successfully, or didn't need to
	r = LIBUSB_SUCCESS;

init_exit:
	if (!concurrent_usage && r != LIBUSB_SUCCESS) { // First init failed?
		exit_polling();
		windows_common_exit();
		unload_usbdk_helper_dll();
	}

	if (r != LIBUSB_SUCCESS)
		--concurrent_usage; // Not expected to call libusb_exit if we failed.

	return r;
}

static int usbdk_get_session_id_for_device(struct libusb_context *ctx,
	PUSB_DK_DEVICE_ID id, unsigned long *session_id)
{
	char dev_identity[ARRAYSIZE(id->DeviceID) + ARRAYSIZE(id->InstanceID)];

	if (sprintf(dev_identity, "%S%S", id->DeviceID, id->InstanceID) == -1) {
		usbi_warn(ctx, "cannot form device identity", id->DeviceID);
		return LIBUSB_ERROR_NOT_SUPPORTED;
	}

	*session_id = htab_hash(dev_identity);

	return LIBUSB_SUCCESS;
}

static void usbdk_release_config_descriptors(struct usbdk_device_priv *p, uint8_t count)
{
	uint8_t i;

	for (i = 0; i < count; i++)
		usbdk_helper.ReleaseConfigurationDescriptor(p->config_descriptors[i]);

	free(p->config_descriptors);
	p->config_descriptors = NULL;
}

static int usbdk_cache_config_descriptors(struct libusb_context *ctx,
	struct usbdk_device_priv *p, PUSB_DK_DEVICE_INFO info)
{
	uint8_t i;
	USB_DK_CONFIG_DESCRIPTOR_REQUEST Request;
	Request.ID = info->ID;

	p->config_descriptors = calloc(info->DeviceDescriptor.bNumConfigurations, sizeof(PUSB_CONFIGURATION_DESCRIPTOR));
	if (p->config_descriptors == NULL) {
		usbi_err(ctx, "failed to allocate configuration descriptors holder");
		return LIBUSB_ERROR_NO_MEM;
	}

	for (i = 0; i < info->DeviceDescriptor.bNumConfigurations; i++) {
		ULONG Length;

		Request.Index = i;
		if (!usbdk_helper.GetConfigurationDescriptor(&Request, &p->config_descriptors[i], &Length)) {
			usbi_err(ctx, "failed to retrieve configuration descriptors");
			usbdk_release_config_descriptors(p, i);
			return LIBUSB_ERROR_OTHER;
		}
	}

	return LIBUSB_SUCCESS;
}

static inline int usbdk_device_priv_init(struct libusb_context *ctx, struct libusb_device *dev, PUSB_DK_DEVICE_INFO info)
{
	struct usbdk_device_priv *p = _usbdk_device_priv(dev);

	p->info = *info;
	p->active_configuration = 0;

	return usbdk_cache_config_descriptors(ctx, p, info);
}

static void usbdk_device_init(libusb_device *dev, PUSB_DK_DEVICE_INFO info)
{
	dev->bus_number = (uint8_t)info->FilterID;
	dev->port_number = (uint8_t)info->Port;
	dev->parent_dev = NULL;

	//Addresses in libusb are 1-based
	dev->device_address = (uint8_t)(info->Port + 1);

	dev->num_configurations = info->DeviceDescriptor.bNumConfigurations;
	dev->device_descriptor = info->DeviceDescriptor;

	switch (info->Speed) {
	case LowSpeed:
		dev->speed = LIBUSB_SPEED_LOW;
		break;
	case FullSpeed:
		dev->speed = LIBUSB_SPEED_FULL;
		break;
	case HighSpeed:
		dev->speed = LIBUSB_SPEED_HIGH;
		break;
	case SuperSpeed:
		dev->speed = LIBUSB_SPEED_SUPER;
		break;
	case NoSpeed:
	default:
		dev->speed = LIBUSB_SPEED_UNKNOWN;
		break;
	}
}

static int usbdk_get_device_list(struct libusb_context *ctx, struct discovered_devs **_discdevs)
{
	int r = LIBUSB_SUCCESS;
	ULONG i;
	struct discovered_devs *discdevs = NULL;
	ULONG dev_number;
	PUSB_DK_DEVICE_INFO devices;

	if(!usbdk_helper.GetDevicesList(&devices, &dev_number))
		return LIBUSB_ERROR_OTHER;

	for (i = 0; i < dev_number; i++) {
		unsigned long session_id;
		struct libusb_device *dev = NULL;

		if (usbdk_get_session_id_for_device(ctx, &devices[i].ID, &session_id))
			continue;

		dev = usbi_get_device_by_session_id(ctx, session_id);
		if (dev == NULL) {
			dev = usbi_alloc_device(ctx, session_id);
			if (dev == NULL) {
				usbi_err(ctx, "failed to allocate a new device structure");
				continue;
			}

			usbdk_device_init(dev, &devices[i]);
			if (usbdk_device_priv_init(ctx, dev, &devices[i]) != LIBUSB_SUCCESS) {
				libusb_unref_device(dev);
				continue;
			}
		}

		discdevs = discovered_devs_append(*_discdevs, dev);
		libusb_unref_device(dev);
		if (!discdevs) {
			usbi_err(ctx, "cannot append new device to list");
			r = LIBUSB_ERROR_NO_MEM;
			goto func_exit;
		}

		*_discdevs = discdevs;
	}

func_exit:
	usbdk_helper.ReleaseDevicesList(devices);
	return r;
}

static void usbdk_exit(void)
{
	if (--concurrent_usage < 0) {
		windows_common_exit();
		exit_polling();
		unload_usbdk_helper_dll();
	}
}

static int usbdk_get_device_descriptor(struct libusb_device *dev, unsigned char *buffer, int *host_endian)
{
	struct usbdk_device_priv *priv = _usbdk_device_priv(dev);

	memcpy(buffer, &priv->info.DeviceDescriptor, DEVICE_DESC_LENGTH);
	*host_endian = 0;

	return LIBUSB_SUCCESS;
}

static int usbdk_get_config_descriptor(struct libusb_device *dev, uint8_t config_index, unsigned char *buffer, size_t len, int *host_endian)
{
	struct usbdk_device_priv *priv = _usbdk_device_priv(dev);
	PUSB_CONFIGURATION_DESCRIPTOR config_header;
	size_t size;

	if (config_index >= dev->num_configurations)
		return LIBUSB_ERROR_INVALID_PARAM;

	config_header = (PUSB_CONFIGURATION_DESCRIPTOR)priv->config_descriptors[config_index];

	size = min(config_header->wTotalLength, len);
	memcpy(buffer, config_header, size);
	*host_endian = 0;

	return (int)size;
}

static inline int usbdk_get_active_config_descriptor(struct libusb_device *dev, unsigned char *buffer, size_t len, int *host_endian)
{
	return usbdk_get_config_descriptor(dev, _usbdk_device_priv(dev)->active_configuration,
			buffer, len, host_endian);
}

static int usbdk_open(struct libusb_device_handle *dev_handle)
{
	struct usbdk_device_priv *priv = _usbdk_device_priv(dev_handle->dev);

	priv->redirector_handle = usbdk_helper.StartRedirect(&priv->info.ID);
	if (priv->redirector_handle == INVALID_HANDLE_VALUE) {
		usbi_err(DEVICE_CTX(dev_handle->dev), "Redirector startup failed");
		return LIBUSB_ERROR_OTHER;
	}

	return LIBUSB_SUCCESS;
}

static void usbdk_close(struct libusb_device_handle *dev_handle)
{
	struct usbdk_device_priv *priv = _usbdk_device_priv(dev_handle->dev);

	if (!usbdk_helper.StopRedirect(priv->redirector_handle)) {
		struct libusb_context *ctx = DEVICE_CTX(dev_handle->dev);
		usbi_err(ctx, "Redirector shutdown failed");
	}
}

static int usbdk_get_configuration(struct libusb_device_handle *dev_handle, int *config)
{
	*config = _usbdk_device_priv(dev_handle->dev)->active_configuration;

	return LIBUSB_SUCCESS;
}

static int usbdk_set_configuration(struct libusb_device_handle *dev_handle, int config)
{
	UNUSED(dev_handle);
	UNUSED(config);
	return LIBUSB_SUCCESS;
}

static int usbdk_claim_interface(struct libusb_device_handle *dev_handle, int iface)
{
	UNUSED(dev_handle);
	UNUSED(iface);
	return LIBUSB_SUCCESS;
}

static int usbdk_set_interface_altsetting(struct libusb_device_handle *dev_handle, int iface, int altsetting)
{
	struct libusb_context *ctx = DEVICE_CTX(dev_handle->dev);
	struct usbdk_device_priv *priv = _usbdk_device_priv(dev_handle->dev);

	if (!usbdk_helper.SetAltsetting(priv->redirector_handle, iface, altsetting)) {
		usbi_err(ctx, "SetAltsetting failed: %s", windows_error_str(0));
		return LIBUSB_ERROR_NO_DEVICE;
	}

	return LIBUSB_SUCCESS;
}

static int usbdk_release_interface(struct libusb_device_handle *dev_handle, int iface)
{
	UNUSED(dev_handle);
	UNUSED(iface);
	return LIBUSB_SUCCESS;
}

static int usbdk_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint)
{
	struct libusb_context *ctx = DEVICE_CTX(dev_handle->dev);
	struct usbdk_device_priv *priv = _usbdk_device_priv(dev_handle->dev);

	if (!usbdk_helper.ResetPipe(priv->redirector_handle, endpoint)) {
		usbi_err(ctx, "ResetPipe failed: %s", windows_error_str(0));
		return LIBUSB_ERROR_NO_DEVICE;
	}

	return LIBUSB_SUCCESS;
}

static int usbdk_reset_device(struct libusb_device_handle *dev_handle)
{
	struct libusb_context *ctx = DEVICE_CTX(dev_handle->dev);
	struct usbdk_device_priv *priv = _usbdk_device_priv(dev_handle->dev);

	if (!usbdk_helper.ResetDevice(priv->redirector_handle)) {
		usbi_err(ctx, "ResetDevice failed: %s", windows_error_str(0));
		return LIBUSB_ERROR_NO_DEVICE;
	}

	return LIBUSB_SUCCESS;
}

static int usbdk_kernel_driver_active(struct libusb_device_handle *dev_handle, int iface)
{
	UNUSED(dev_handle);
	UNUSED(iface);
	return LIBUSB_ERROR_NOT_SUPPORTED;
}

static int usbdk_attach_kernel_driver(struct libusb_device_handle *dev_handle, int iface)
{
	UNUSED(dev_handle);
	UNUSED(iface);
	return LIBUSB_ERROR_NOT_SUPPORTED;
}

static int usbdk_detach_kernel_driver(struct libusb_device_handle *dev_handle, int iface)
{
	UNUSED(dev_handle);
	UNUSED(iface);
	return LIBUSB_ERROR_NOT_SUPPORTED;
}

static void usbdk_destroy_device(struct libusb_device *dev)
{
	struct usbdk_device_priv* p = _usbdk_device_priv(dev);

	if (p->config_descriptors != NULL)
		usbdk_release_config_descriptors(p, p->info.DeviceDescriptor.bNumConfigurations);
}

void windows_clear_transfer_priv(struct usbi_transfer *itransfer)
{
	struct usbdk_transfer_priv *transfer_priv = _usbdk_transfer_priv(itransfer);
	struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);

	usbi_free_fd(&transfer_priv->pollable_fd);

	if (transfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) {
		safe_free(transfer_priv->IsochronousPacketsArray);
		safe_free(transfer_priv->IsochronousResultsArray);
	}
}

static int usbdk_do_control_transfer(struct usbi_transfer *itransfer)
{
	struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
	struct usbdk_device_priv *priv = _usbdk_device_priv(transfer->dev_handle->dev);
	struct usbdk_transfer_priv *transfer_priv = _usbdk_transfer_priv(itransfer);
	struct libusb_context *ctx = DEVICE_CTX(transfer->dev_handle->dev);
	struct winfd wfd;
	ULONG Length;
	TransferResult transResult;
	HANDLE sysHandle;

	sysHandle = usbdk_helper.GetRedirectorSystemHandle(priv->redirector_handle);

	wfd = usbi_create_fd(sysHandle, RW_READ, NULL, NULL);
	// Always use the handle returned from usbi_create_fd (wfd.handle)
	if (wfd.fd < 0)
		return LIBUSB_ERROR_NO_MEM;

	transfer_priv->request.Buffer = (PVOID64)(uintptr_t)transfer->buffer;
	transfer_priv->request.BufferLength = transfer->length;
	transfer_priv->request.TransferType = ControlTransferType;
	transfer_priv->pollable_fd = INVALID_WINFD;
	Length = (ULONG)transfer->length;

	if (IS_XFERIN(transfer))
		transResult = usbdk_helper.ReadPipe(priv->redirector_handle, &transfer_priv->request, wfd.overlapped);
	else
		transResult = usbdk_helper.WritePipe(priv->redirector_handle, &transfer_priv->request, wfd.overlapped);

	switch (transResult) {
	case TransferSuccess:
		wfd.overlapped->Internal = STATUS_COMPLETED_SYNCHRONOUSLY;
		wfd.overlapped->InternalHigh = (DWORD)Length;
		break;
	case TransferSuccessAsync:
		break;
	case TransferFailure:
		usbi_err(ctx, "ControlTransfer failed: %s", windows_error_str(0));
		usbi_free_fd(&wfd);
		return LIBUSB_ERROR_IO;
	}

	// Use priv_transfer to store data needed for async polling
	transfer_priv->pollable_fd = wfd;
	usbi_add_pollfd(ctx, transfer_priv->pollable_fd.fd, POLLIN);

	return LIBUSB_SUCCESS;
}

static int usbdk_do_bulk_transfer(struct usbi_transfer *itransfer)
{
	struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
	struct usbdk_device_priv *priv = _usbdk_device_priv(transfer->dev_handle->dev);
	struct usbdk_transfer_priv *transfer_priv = _usbdk_transfer_priv(itransfer);
	struct libusb_context *ctx = DEVICE_CTX(transfer->dev_handle->dev);
	struct winfd wfd;
	TransferResult transferRes;
	HANDLE sysHandle;

	transfer_priv->request.Buffer = (PVOID64)(uintptr_t)transfer->buffer;
	transfer_priv->request.BufferLength = transfer->length;
	transfer_priv->request.EndpointAddress = transfer->endpoint;

	switch (transfer->type) {
	case LIBUSB_TRANSFER_TYPE_BULK:
		transfer_priv->request.TransferType = BulkTransferType;
		break;
	case LIBUSB_TRANSFER_TYPE_INTERRUPT:
		transfer_priv->request.TransferType = IntertuptTransferType;
		break;
	default:
		usbi_err(ctx, "Wrong transfer type (%d) in usbdk_do_bulk_transfer. %s", transfer->type, windows_error_str(0));
		return LIBUSB_ERROR_INVALID_PARAM;
	}

	transfer_priv->pollable_fd = INVALID_WINFD;

	sysHandle = usbdk_helper.GetRedirectorSystemHandle(priv->redirector_handle);

	wfd = usbi_create_fd(sysHandle, IS_XFERIN(transfer) ? RW_READ : RW_WRITE, NULL, NULL);
	// Always use the handle returned from usbi_create_fd (wfd.handle)
	if (wfd.fd < 0)
		return LIBUSB_ERROR_NO_MEM;

	if (IS_XFERIN(transfer))
		transferRes = usbdk_helper.ReadPipe(priv->redirector_handle, &transfer_priv->request, wfd.overlapped);
	else
		transferRes = usbdk_helper.WritePipe(priv->redirector_handle, &transfer_priv->request, wfd.overlapped);

	switch (transferRes) {
	case TransferSuccess:
		wfd.overlapped->Internal = STATUS_COMPLETED_SYNCHRONOUSLY;
		break;
	case TransferSuccessAsync:
		break;
	case TransferFailure:
		usbi_err(ctx, "ReadPipe/WritePipe failed: %s", windows_error_str(0));
		usbi_free_fd(&wfd);
		return LIBUSB_ERROR_IO;
	}

	transfer_priv->pollable_fd = wfd;
	usbi_add_pollfd(ctx, transfer_priv->pollable_fd.fd, IS_XFERIN(transfer) ? POLLIN : POLLOUT);

	return LIBUSB_SUCCESS;
}

static int usbdk_do_iso_transfer(struct usbi_transfer *itransfer)
{
	struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
	struct usbdk_device_priv *priv = _usbdk_device_priv(transfer->dev_handle->dev);
	struct usbdk_transfer_priv *transfer_priv = _usbdk_transfer_priv(itransfer);
	struct libusb_context *ctx = DEVICE_CTX(transfer->dev_handle->dev);
	struct winfd wfd;
	TransferResult transferRes;
	int i;
	HANDLE sysHandle;

	transfer_priv->request.Buffer = (PVOID64)(uintptr_t)transfer->buffer;
	transfer_priv->request.BufferLength = transfer->length;
	transfer_priv->request.EndpointAddress = transfer->endpoint;
	transfer_priv->request.TransferType = IsochronousTransferType;
	transfer_priv->request.IsochronousPacketsArraySize = transfer->num_iso_packets;
	transfer_priv->IsochronousPacketsArray = malloc(transfer->num_iso_packets * sizeof(ULONG64));
	transfer_priv->request.IsochronousPacketsArray = (PVOID64)(uintptr_t)transfer_priv->IsochronousPacketsArray;
	if (!transfer_priv->IsochronousPacketsArray) {
		usbi_err(ctx, "Allocation of IsochronousPacketsArray is failed, %s", windows_error_str(0));
		return LIBUSB_ERROR_IO;
	}

	transfer_priv->IsochronousResultsArray = malloc(transfer->num_iso_packets * sizeof(USB_DK_ISO_TRANSFER_RESULT));
	transfer_priv->request.Result.IsochronousResultsArray = (PVOID64)(uintptr_t)transfer_priv->IsochronousResultsArray;
	if (!transfer_priv->IsochronousResultsArray) {
		usbi_err(ctx, "Allocation of isochronousResultsArray is failed, %s", windows_error_str(0));
		free(transfer_priv->IsochronousPacketsArray);
		return LIBUSB_ERROR_IO;
	}

	for (i = 0; i < transfer->num_iso_packets; i++)
		transfer_priv->IsochronousPacketsArray[i] = transfer->iso_packet_desc[i].length;

	transfer_priv->pollable_fd = INVALID_WINFD;

	sysHandle = usbdk_helper.GetRedirectorSystemHandle(priv->redirector_handle);

	wfd = usbi_create_fd(sysHandle, IS_XFERIN(transfer) ? RW_READ : RW_WRITE, NULL, NULL);
	// Always use the handle returned from usbi_create_fd (wfd.handle)
	if (wfd.fd < 0) {
		free(transfer_priv->IsochronousPacketsArray);
		free(transfer_priv->IsochronousResultsArray);
		return LIBUSB_ERROR_NO_MEM;
	}

	if (IS_XFERIN(transfer))
		transferRes = usbdk_helper.ReadPipe(priv->redirector_handle, &transfer_priv->request, wfd.overlapped);
	else
		transferRes = usbdk_helper.WritePipe(priv->redirector_handle, &transfer_priv->request, wfd.overlapped);

	switch (transferRes) {
	case TransferSuccess:
		wfd.overlapped->Internal = STATUS_COMPLETED_SYNCHRONOUSLY;
		break;
	case TransferSuccessAsync:
		break;
	case TransferFailure:
		usbi_err(ctx, "ReadPipe/WritePipe failed: %s", windows_error_str(0));
		usbi_free_fd(&wfd);
		free(transfer_priv->IsochronousPacketsArray);
		free(transfer_priv->IsochronousResultsArray);
		return LIBUSB_ERROR_IO;
	}

	transfer_priv->pollable_fd = wfd;
	usbi_add_pollfd(ctx, transfer_priv->pollable_fd.fd, IS_XFERIN(transfer) ? POLLIN : POLLOUT);

	return LIBUSB_SUCCESS;
}

static int usbdk_submit_transfer(struct usbi_transfer *itransfer)
{
	struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);

	switch (transfer->type) {
	case LIBUSB_TRANSFER_TYPE_CONTROL:
		return usbdk_do_control_transfer(itransfer);
	case LIBUSB_TRANSFER_TYPE_BULK:
	case LIBUSB_TRANSFER_TYPE_INTERRUPT:
		if (IS_XFEROUT(transfer) && (transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET))
			return LIBUSB_ERROR_NOT_SUPPORTED; //TODO: Check whether we can support this in UsbDk
		else
			return usbdk_do_bulk_transfer(itransfer);
	case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
		return usbdk_do_iso_transfer(itransfer);
	default:
		usbi_err(TRANSFER_CTX(transfer), "unknown endpoint type %d", transfer->type);
		return LIBUSB_ERROR_INVALID_PARAM;
	}
}

static int usbdk_abort_transfers(struct usbi_transfer *itransfer)
{
	struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
	struct libusb_context *ctx = DEVICE_CTX(transfer->dev_handle->dev);
	struct usbdk_device_priv *priv = _usbdk_device_priv(transfer->dev_handle->dev);

	if (!usbdk_helper.AbortPipe(priv->redirector_handle, transfer->endpoint)) {
		usbi_err(ctx, "AbortPipe failed: %s", windows_error_str(0));
		return LIBUSB_ERROR_NO_DEVICE;
	}

	return LIBUSB_SUCCESS;
}

static int usbdk_cancel_transfer(struct usbi_transfer *itransfer)
{
	struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);

	switch (transfer->type) {
	case LIBUSB_TRANSFER_TYPE_CONTROL:
		// Control transfers cancelled by IoCancelXXX() API
		// No special treatment needed
		return LIBUSB_SUCCESS;
	case LIBUSB_TRANSFER_TYPE_BULK:
	case LIBUSB_TRANSFER_TYPE_INTERRUPT:
	case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
		return usbdk_abort_transfers(itransfer);
	default:
		usbi_err(ITRANSFER_CTX(itransfer), "unknown endpoint type %d", transfer->type);
		return LIBUSB_ERROR_INVALID_PARAM;
	}
}

int windows_copy_transfer_data(struct usbi_transfer *itransfer, uint32_t io_size)
{
	itransfer->transferred += io_size;
	return LIBUSB_TRANSFER_COMPLETED;
}

struct winfd *windows_get_fd(struct usbi_transfer *transfer)
{
	struct usbdk_transfer_priv *transfer_priv = _usbdk_transfer_priv(transfer);
	return &transfer_priv->pollable_fd;
}

static DWORD usbdk_translate_usbd_status(USBD_STATUS UsbdStatus)
{
	if (USBD_SUCCESS(UsbdStatus))
		return NO_ERROR;

	switch (UsbdStatus) {
	case USBD_STATUS_STALL_PID:
	case USBD_STATUS_ENDPOINT_HALTED:
	case USBD_STATUS_BAD_START_FRAME:
		return ERROR_GEN_FAILURE;
	case USBD_STATUS_TIMEOUT:
		return ERROR_SEM_TIMEOUT;
	case USBD_STATUS_CANCELED:
		return ERROR_OPERATION_ABORTED;
	default:
		return ERROR_FUNCTION_FAILED;
	}
}

void windows_get_overlapped_result(struct usbi_transfer *transfer, struct winfd *pollable_fd, DWORD *io_result, DWORD *io_size)
{
	if (HasOverlappedIoCompletedSync(pollable_fd->overlapped) // Handle async requests that completed synchronously first
			|| GetOverlappedResult(pollable_fd->handle, pollable_fd->overlapped, io_size, false)) { // Regular async overlapped
		struct libusb_transfer *ltransfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(transfer);
		struct usbdk_transfer_priv *transfer_priv = _usbdk_transfer_priv(transfer);

		if (ltransfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) {
			int i;
			for (i = 0; i < transfer_priv->request.IsochronousPacketsArraySize; i++) {
				struct libusb_iso_packet_descriptor *lib_desc = &ltransfer->iso_packet_desc[i];

				switch (transfer_priv->IsochronousResultsArray[i].TransferResult) {
				case STATUS_SUCCESS:
				case STATUS_CANCELLED:
				case STATUS_REQUEST_CANCELED:
					lib_desc->status = LIBUSB_TRANSFER_COMPLETED; // == ERROR_SUCCESS
					break;
				default:
					lib_desc->status = LIBUSB_TRANSFER_ERROR; // ERROR_UNKNOWN_EXCEPTION;
					break;
				}

				lib_desc->actual_length = (unsigned int)transfer_priv->IsochronousResultsArray[i].ActualLength;
			}
		}

		*io_size = (DWORD) transfer_priv->request.Result.GenResult.BytesTransferred;
		*io_result = usbdk_translate_usbd_status((USBD_STATUS) transfer_priv->request.Result.GenResult.UsbdStatus);
	}
	else {
		*io_result = GetLastError();
	}
}

static int usbdk_clock_gettime(int clk_id, struct timespec *tp)
{
	return windows_clock_gettime(clk_id, tp);
}

const struct usbi_os_backend usbdk_backend = {
	"Windows",
	USBI_CAP_HAS_HID_ACCESS,
	usbdk_init,
	usbdk_exit,

	usbdk_get_device_list,
	NULL,
	usbdk_open,
	usbdk_close,

	usbdk_get_device_descriptor,
	usbdk_get_active_config_descriptor,
	usbdk_get_config_descriptor,
	NULL,

	usbdk_get_configuration,
	usbdk_set_configuration,
	usbdk_claim_interface,
	usbdk_release_interface,

	usbdk_set_interface_altsetting,
	usbdk_clear_halt,
	usbdk_reset_device,

	NULL,
	NULL,

	NULL,	// dev_mem_alloc()
	NULL,	// dev_mem_free()

	usbdk_kernel_driver_active,
	usbdk_detach_kernel_driver,
	usbdk_attach_kernel_driver,

	usbdk_destroy_device,

	usbdk_submit_transfer,
	usbdk_cancel_transfer,
	windows_clear_transfer_priv,

	windows_handle_events,
	NULL,

	usbdk_clock_gettime,
#if defined(USBI_TIMERFD_AVAILABLE)
	NULL,
#endif
	sizeof(struct usbdk_device_priv),
	0,
	sizeof(struct usbdk_transfer_priv),
};

#endif /* USE_USBDK */
