/**
 * FreeRDP: A Remote Desktop Protocol Implementation
 * RemoteFX USB Redirection
 *
 * Copyright 2012 Atrust corp.
 * Copyright 2012 Alfred Liu <alfred.liu@atruscorp.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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

#include <winpr/assert.h>
#include <winpr/cast.h>
#include <winpr/wtypes.h>
#include <winpr/sysinfo.h>
#include <winpr/collections.h>

#include <errno.h>

#include "libusb_udevice.h"
#include "msusb.h"
#include "../common/urbdrc_types.h"

#define BASIC_STATE_FUNC_DEFINED(_arg, _type)             \
	static _type udev_get_##_arg(IUDEVICE* idev)          \
	{                                                     \
		UDEVICE* pdev = (UDEVICE*)idev;                   \
		return pdev->_arg;                                \
	}                                                     \
	static void udev_set_##_arg(IUDEVICE* idev, _type _t) \
	{                                                     \
		UDEVICE* pdev = (UDEVICE*)idev;                   \
		pdev->_arg = _t;                                  \
	}

#define BASIC_POINT_FUNC_DEFINED(_arg, _type)               \
	static _type udev_get_p_##_arg(IUDEVICE* idev)          \
	{                                                       \
		UDEVICE* pdev = (UDEVICE*)idev;                     \
		return pdev->_arg;                                  \
	}                                                       \
	static void udev_set_p_##_arg(IUDEVICE* idev, _type _t) \
	{                                                       \
		UDEVICE* pdev = (UDEVICE*)idev;                     \
		pdev->_arg = _t;                                    \
	}

#define BASIC_STATE_FUNC_REGISTER(_arg, _dev) \
	_dev->iface.get_##_arg = udev_get_##_arg; \
	(_dev)->iface.set_##_arg = udev_set_##_arg

#if LIBUSB_API_VERSION >= 0x01000103
#define HAVE_STREAM_ID_API 1
#endif

typedef struct
{
	wStream* data;
	BOOL noack;
	UINT32 MessageId;
	UINT32 StartFrame;
	UINT32 ErrorCount;
	IUDEVICE* idev;
	UINT32 OutputBufferSize;
	GENERIC_CHANNEL_CALLBACK* callback;
	t_isoch_transfer_cb cb;
	wArrayList* queue;
#if !defined(HAVE_STREAM_ID_API)
	UINT32 streamID;
#endif
} ASYNC_TRANSFER_USER_DATA;

static void request_free(void* value);

static struct libusb_transfer* list_contains(wArrayList* list, UINT32 streamID)
{
	size_t count = 0;
	if (!list)
		return nullptr;
	count = ArrayList_Count(list);
	for (size_t x = 0; x < count; x++)
	{
		struct libusb_transfer* transfer = ArrayList_GetItem(list, x);

#if defined(HAVE_STREAM_ID_API)
		const UINT32 currentID = libusb_transfer_get_stream_id(transfer);
#else
		const ASYNC_TRANSFER_USER_DATA* user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data;
		const UINT32 currentID = user_data->streamID;
#endif
		if (currentID == streamID)
			return transfer;
	}
	return nullptr;
}

static UINT32 stream_id_from_buffer(struct libusb_transfer* transfer)
{
	if (!transfer)
		return 0;
#if defined(HAVE_STREAM_ID_API)
	return libusb_transfer_get_stream_id(transfer);
#else
	ASYNC_TRANSFER_USER_DATA* user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data;
	if (!user_data)
		return 0;
	return user_data->streamID;
#endif
}

static void set_stream_id_for_buffer(struct libusb_transfer* transfer, UINT32 streamID)
{
#if defined(HAVE_STREAM_ID_API)
	libusb_transfer_set_stream_id(transfer, streamID);
#else
	ASYNC_TRANSFER_USER_DATA* user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data;
	if (!user_data)
		return;
	user_data->streamID = streamID;
#endif
}

WINPR_ATTR_FORMAT_ARG(3, 8)
static BOOL log_libusb_result_(wLog* log, DWORD lvl, WINPR_FORMAT_ARG const char* fmt,
                               const char* fkt, const char* file, size_t line, int error, ...)
{
	WINPR_UNUSED(file);

	if (error < 0)
	{
		char buffer[8192] = WINPR_C_ARRAY_INIT;
		va_list ap = WINPR_C_ARRAY_INIT;
		va_start(ap, error);
		(void)vsnprintf(buffer, sizeof(buffer), fmt, ap);
		va_end(ap);

		WLog_Print(log, lvl, "[%s:%" PRIuz "]: %s: error %s[%d]", fkt, line, buffer,
		           libusb_error_name(error), error);
		return TRUE;
	}
	return FALSE;
}

#define log_libusb_result(log, lvl, fmt, error, ...) \
	log_libusb_result_((log), (lvl), (fmt), __func__, __FILE__, __LINE__, error, ##__VA_ARGS__)

const char* usb_interface_class_to_string(uint8_t c_class)
{
	switch (c_class)
	{
		case LIBUSB_CLASS_PER_INTERFACE:
			return "LIBUSB_CLASS_PER_INTERFACE";
		case LIBUSB_CLASS_AUDIO:
			return "LIBUSB_CLASS_AUDIO";
		case LIBUSB_CLASS_COMM:
			return "LIBUSB_CLASS_COMM";
		case LIBUSB_CLASS_HID:
			return "LIBUSB_CLASS_HID";
		case LIBUSB_CLASS_PHYSICAL:
			return "LIBUSB_CLASS_PHYSICAL";
		case LIBUSB_CLASS_PRINTER:
			return "LIBUSB_CLASS_PRINTER";
		case LIBUSB_CLASS_IMAGE:
			return "LIBUSB_CLASS_IMAGE";
		case LIBUSB_CLASS_MASS_STORAGE:
			return "LIBUSB_CLASS_MASS_STORAGE";
		case LIBUSB_CLASS_HUB:
			return "LIBUSB_CLASS_HUB";
		case LIBUSB_CLASS_DATA:
			return "LIBUSB_CLASS_DATA";
		case LIBUSB_CLASS_SMART_CARD:
			return "LIBUSB_CLASS_SMART_CARD";
		case LIBUSB_CLASS_CONTENT_SECURITY:
			return "LIBUSB_CLASS_CONTENT_SECURITY";
		case LIBUSB_CLASS_VIDEO:
			return "LIBUSB_CLASS_VIDEO";
		case LIBUSB_CLASS_PERSONAL_HEALTHCARE:
			return "LIBUSB_CLASS_PERSONAL_HEALTHCARE";
		case LIBUSB_CLASS_DIAGNOSTIC_DEVICE:
			return "LIBUSB_CLASS_DIAGNOSTIC_DEVICE";
		case LIBUSB_CLASS_WIRELESS:
			return "LIBUSB_CLASS_WIRELESS";
		case LIBUSB_CLASS_APPLICATION:
			return "LIBUSB_CLASS_APPLICATION";
		case LIBUSB_CLASS_VENDOR_SPEC:
			return "LIBUSB_CLASS_VENDOR_SPEC";
		default:
			return "UNKNOWN_DEVICE_CLASS";
	}
}

static ASYNC_TRANSFER_USER_DATA* async_transfer_user_data_new(IUDEVICE* idev, UINT32 MessageId,
                                                              size_t offset, size_t BufferSize,
                                                              const BYTE* data, size_t packetSize,
                                                              BOOL NoAck, t_isoch_transfer_cb cb,
                                                              GENERIC_CHANNEL_CALLBACK* callback)
{
	ASYNC_TRANSFER_USER_DATA* user_data = nullptr;
	UDEVICE* pdev = (UDEVICE*)idev;

	if (BufferSize > UINT32_MAX)
		return nullptr;

	user_data = calloc(1, sizeof(ASYNC_TRANSFER_USER_DATA));
	if (!user_data)
		return nullptr;

	user_data->data = Stream_New(nullptr, offset + BufferSize + packetSize);

	if (!user_data->data)
	{
		free(user_data);
		return nullptr;
	}

	Stream_Seek(user_data->data, offset); /* Skip header offset */
	if (data)
		memcpy(Stream_Pointer(user_data->data), data, BufferSize);
	else
		user_data->OutputBufferSize = (UINT32)BufferSize;

	user_data->noack = NoAck;
	user_data->cb = cb;
	user_data->callback = callback;
	user_data->idev = idev;
	user_data->MessageId = MessageId;

	user_data->queue = pdev->request_queue;

	return user_data;
}

static void async_transfer_user_data_free(ASYNC_TRANSFER_USER_DATA* user_data)
{
	if (user_data)
	{
		Stream_Free(user_data->data, TRUE);
		free(user_data);
	}
}

static void LIBUSB_CALL func_iso_callback(struct libusb_transfer* transfer)
{
	ASYNC_TRANSFER_USER_DATA* user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data;
	const UINT32 streamID = stream_id_from_buffer(transfer);
	wArrayList* list = user_data->queue;

	ArrayList_Lock(list);
	switch (transfer->status)
	{
		case LIBUSB_TRANSFER_COMPLETED:
		{
			UINT32 index = 0;
			BYTE* dataStart = Stream_Pointer(user_data->data);
			if (!Stream_SetPosition(user_data->data,
			                        40)) /* TS_URB_ISOCH_TRANSFER_RESULT IsoPacket offset */
				break;

			for (uint32_t i = 0; i < WINPR_ASSERTING_INT_CAST(uint32_t, transfer->num_iso_packets);
			     i++)
			{
				const UINT32 act_len = transfer->iso_packet_desc[i].actual_length;
				Stream_Write_UINT32(user_data->data, index);
				Stream_Write_UINT32(user_data->data, act_len);
				Stream_Write_UINT32(user_data->data, transfer->iso_packet_desc[i].status);

				if (transfer->iso_packet_desc[i].status != USBD_STATUS_SUCCESS)
					user_data->ErrorCount++;
				else
				{
					const unsigned char* packetBuffer =
					    libusb_get_iso_packet_buffer_simple(transfer, i);
					BYTE* data = dataStart + index;

					if (data != packetBuffer)
						memmove(data, packetBuffer, act_len);

					index += act_len;
				}
			}
		}
			/* fallthrough */
			WINPR_FALLTHROUGH
		case LIBUSB_TRANSFER_CANCELLED:
			/* fallthrough */
			WINPR_FALLTHROUGH
		case LIBUSB_TRANSFER_TIMED_OUT:
			/* fallthrough */
			WINPR_FALLTHROUGH
		case LIBUSB_TRANSFER_ERROR:
		{
			const UINT32 InterfaceId =
			    ((STREAM_ID_PROXY << 30) | user_data->idev->get_ReqCompletion(user_data->idev));

			if (list_contains(list, streamID))
			{
				if (!user_data->noack)
				{
					const UINT32 RequestID = streamID & INTERFACE_ID_MASK;
					user_data->cb(user_data->idev, user_data->callback, user_data->data,
					              InterfaceId, user_data->noack, user_data->MessageId, RequestID,
					              WINPR_ASSERTING_INT_CAST(uint32_t, transfer->num_iso_packets),
					              transfer->status, user_data->StartFrame, user_data->ErrorCount,
					              user_data->OutputBufferSize);
					user_data->data = nullptr;
				}
				ArrayList_Remove(list, transfer);
			}
		}
		break;
		default:
			break;
	}
	ArrayList_Unlock(list);
}

static const LIBUSB_ENDPOINT_DESCEIPTOR* func_get_ep_desc(LIBUSB_CONFIG_DESCRIPTOR* LibusbConfig,
                                                          MSUSB_CONFIG_DESCRIPTOR* MsConfig,
                                                          UINT32 EndpointAddress)
{
	MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces = MsConfig->MsInterfaces;
	const LIBUSB_INTERFACE* interface = LibusbConfig->interface;

	for (UINT32 inum = 0; inum < MsConfig->NumInterfaces; inum++)
	{
		BYTE alt = MsInterfaces[inum]->AlternateSetting;
		const LIBUSB_ENDPOINT_DESCEIPTOR* endpoint = interface[inum].altsetting[alt].endpoint;

		for (UINT32 pnum = 0; pnum < MsInterfaces[inum]->NumberOfPipes; pnum++)
		{
			if (endpoint[pnum].bEndpointAddress == EndpointAddress)
			{
				return &endpoint[pnum];
			}
		}
	}

	return nullptr;
}

static void LIBUSB_CALL func_bulk_transfer_cb(struct libusb_transfer* transfer)
{
	ASYNC_TRANSFER_USER_DATA* user_data = nullptr;
	uint32_t streamID = 0;
	wArrayList* list = nullptr;

	user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data;
	if (!user_data)
	{
		WLog_ERR(TAG, "Invalid transfer->user_data!");
		return;
	}
	list = user_data->queue;
	ArrayList_Lock(list);
	streamID = stream_id_from_buffer(transfer);

	if (list_contains(list, streamID))
	{
		const UINT32 InterfaceId =
		    ((STREAM_ID_PROXY << 30) | user_data->idev->get_ReqCompletion(user_data->idev));
		const UINT32 RequestID = streamID & INTERFACE_ID_MASK;

		user_data->cb(user_data->idev, user_data->callback, user_data->data, InterfaceId,
		              user_data->noack, user_data->MessageId, RequestID,
		              WINPR_ASSERTING_INT_CAST(uint32_t, transfer->num_iso_packets),
		              transfer->status, user_data->StartFrame, user_data->ErrorCount,
		              WINPR_ASSERTING_INT_CAST(uint32_t, transfer->actual_length));
		user_data->data = nullptr;
		ArrayList_Remove(list, transfer);
	}
	ArrayList_Unlock(list);
}

static BOOL func_set_usbd_status(URBDRC_PLUGIN* urbdrc, UDEVICE* pdev, UINT32* status,
                                 int err_result)
{
	if (!urbdrc || !status)
		return FALSE;

	switch (err_result)
	{
		case LIBUSB_SUCCESS:
			*status = USBD_STATUS_SUCCESS;
			break;

		case LIBUSB_ERROR_IO:
			*status = USBD_STATUS_STALL_PID;
			break;

		case LIBUSB_ERROR_INVALID_PARAM:
			*status = USBD_STATUS_INVALID_PARAMETER;
			break;

		case LIBUSB_ERROR_ACCESS:
			*status = USBD_STATUS_NOT_ACCESSED;
			break;

		case LIBUSB_ERROR_NO_DEVICE:
			*status = USBD_STATUS_DEVICE_GONE;

			if (pdev)
			{
				if (!(pdev->status & URBDRC_DEVICE_NOT_FOUND))
					pdev->status |= URBDRC_DEVICE_NOT_FOUND;
			}

			break;

		case LIBUSB_ERROR_NOT_FOUND:
			*status = USBD_STATUS_STALL_PID;
			break;

		case LIBUSB_ERROR_BUSY:
			*status = USBD_STATUS_STALL_PID;
			break;

		case LIBUSB_ERROR_TIMEOUT:
			*status = USBD_STATUS_TIMEOUT;
			break;

		case LIBUSB_ERROR_OVERFLOW:
			*status = USBD_STATUS_STALL_PID;
			break;

		case LIBUSB_ERROR_PIPE:
			*status = USBD_STATUS_STALL_PID;
			break;

		case LIBUSB_ERROR_INTERRUPTED:
			*status = USBD_STATUS_STALL_PID;
			break;

		case LIBUSB_ERROR_NO_MEM:
			*status = USBD_STATUS_NO_MEMORY;
			break;

		case LIBUSB_ERROR_NOT_SUPPORTED:
			*status = USBD_STATUS_NOT_SUPPORTED;
			break;

		case LIBUSB_ERROR_OTHER:
			*status = USBD_STATUS_STALL_PID;
			break;

		default:
			*status = USBD_STATUS_SUCCESS;
			break;
	}

	return TRUE;
}

static int func_config_release_all_interface(URBDRC_PLUGIN* urbdrc,
                                             LIBUSB_DEVICE_HANDLE* libusb_handle,
                                             UINT32 NumInterfaces)
{
	if (NumInterfaces > INT32_MAX)
		return -1;
	for (INT32 i = 0; i < (INT32)NumInterfaces; i++)
	{
		int ret = libusb_release_interface(libusb_handle, i);

		if (log_libusb_result(urbdrc->log, WLOG_WARN, "libusb_release_interface", ret))
			return -1;
	}

	return 0;
}

static int func_claim_all_interface(URBDRC_PLUGIN* urbdrc, LIBUSB_DEVICE_HANDLE* libusb_handle,
                                    int NumInterfaces)
{
	int ret = 0;

	for (int i = 0; i < NumInterfaces; i++)
	{
		ret = libusb_claim_interface(libusb_handle, i);

		if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_claim_interface", ret))
			return -1;
	}

	return 0;
}

static LIBUSB_DEVICE* udev_get_libusb_dev(libusb_context* context, uint8_t bus_number,
                                          uint8_t dev_number)
{
	LIBUSB_DEVICE** libusb_list = nullptr;
	LIBUSB_DEVICE* device = nullptr;
	const ssize_t total_device = libusb_get_device_list(context, &libusb_list);

	for (ssize_t i = 0; i < total_device; i++)
	{
		LIBUSB_DEVICE* dev = libusb_list[i];
		if ((bus_number == libusb_get_bus_number(dev)) &&
		    (dev_number == libusb_get_device_address(dev)))
			device = dev;
		else
			libusb_unref_device(dev);
	}

	libusb_free_device_list(libusb_list, 0);
	return device;
}

static LIBUSB_DEVICE_DESCRIPTOR* udev_new_descript(URBDRC_PLUGIN* urbdrc, LIBUSB_DEVICE* libusb_dev)
{
	int ret = 0;
	LIBUSB_DEVICE_DESCRIPTOR* descriptor =
	    (LIBUSB_DEVICE_DESCRIPTOR*)calloc(1, sizeof(LIBUSB_DEVICE_DESCRIPTOR));
	if (!descriptor)
		return nullptr;
	ret = libusb_get_device_descriptor(libusb_dev, descriptor);

	if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_get_device_descriptor", ret))
	{
		free(descriptor);
		return nullptr;
	}

	return descriptor;
}

static int libusb_udev_select_interface(IUDEVICE* idev, BYTE InterfaceNumber, BYTE AlternateSetting)
{
	UDEVICE* pdev = (UDEVICE*)idev;

	if (!pdev || !pdev->urbdrc)
		return -1;

	URBDRC_PLUGIN* urbdrc = pdev->urbdrc;

	const int error =
	    libusb_set_interface_alt_setting(pdev->libusb_handle, InterfaceNumber, AlternateSetting);

	log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_set_interface_alt_setting", error);

	return error;
}

static MSUSB_CONFIG_DESCRIPTOR*
libusb_udev_complete_msconfig_setup(IUDEVICE* idev, MSUSB_CONFIG_DESCRIPTOR* MsConfig)
{
	UDEVICE* pdev = (UDEVICE*)idev;
	UINT32 MsOutSize = 0;

	if (!pdev || !pdev->LibusbConfig || !pdev->urbdrc || !MsConfig)
		return nullptr;

	URBDRC_PLUGIN* urbdrc = pdev->urbdrc;
	LIBUSB_CONFIG_DESCRIPTOR* LibusbConfig = pdev->LibusbConfig;

	if (LibusbConfig->bNumInterfaces != MsConfig->NumInterfaces)
	{
		WLog_Print(urbdrc->log, WLOG_ERROR,
		           "Select Configuration: Libusb NumberInterfaces(%" PRIu8 ") is different "
		           "with MsConfig NumberInterfaces(%" PRIu32 ")",
		           LibusbConfig->bNumInterfaces, MsConfig->NumInterfaces);
		return nullptr;
	}

	/* replace MsPipes for libusb */
	MSUSB_INTERFACE_DESCRIPTOR** MsInterfaces = MsConfig->MsInterfaces;

	for (UINT32 inum = 0; inum < MsConfig->NumInterfaces; inum++)
	{
		MSUSB_INTERFACE_DESCRIPTOR* MsInterface = MsInterfaces[inum];
		if (MsInterface->InterfaceNumber >= MsConfig->NumInterfaces)
		{
			WLog_Print(urbdrc->log, WLOG_ERROR,
			           "MSUSB_CONFIG_DESCRIPTOR::NumInterfaces (%" PRIu32
			           " <= MSUSB_INTERFACE_DESCRIPTOR::InterfaceNumber( %" PRIu8 ")",
			           MsConfig->NumInterfaces, MsInterface->InterfaceNumber);
			return nullptr;
		}

		const LIBUSB_INTERFACE* LibusbInterface =
		    &LibusbConfig->interface[MsInterface->InterfaceNumber];
		if (MsInterface->AlternateSetting >= LibusbInterface->num_altsetting)
		{
			WLog_Print(urbdrc->log, WLOG_ERROR,
			           "LIBUSB_INTERFACE::num_altsetting (%" PRId32
			           " <= MSUSB_INTERFACE_DESCRIPTOR::AlternateSetting( %" PRIu8 ")",
			           LibusbInterface->num_altsetting, MsInterface->AlternateSetting);
			return nullptr;
		}
	}

	for (UINT32 inum = 0; inum < MsConfig->NumInterfaces; inum++)
	{
		MSUSB_INTERFACE_DESCRIPTOR* MsInterface = MsInterfaces[inum];

		/* get libusb's number of endpoints */
		const LIBUSB_INTERFACE* LibusbInterface =
		    &LibusbConfig->interface[MsInterface->InterfaceNumber];
		const LIBUSB_INTERFACE_DESCRIPTOR* LibusbAltsetting =
		    &LibusbInterface->altsetting[MsInterface->AlternateSetting];
		const BYTE LibusbNumEndpoint = LibusbAltsetting->bNumEndpoints;
		MSUSB_PIPE_DESCRIPTOR** t_MsPipes =
		    (MSUSB_PIPE_DESCRIPTOR**)calloc(LibusbNumEndpoint, sizeof(MSUSB_PIPE_DESCRIPTOR*));

		for (UINT32 pnum = 0; pnum < LibusbNumEndpoint; pnum++)
		{
			MSUSB_PIPE_DESCRIPTOR* t_MsPipe =
			    (MSUSB_PIPE_DESCRIPTOR*)calloc(1, sizeof(MSUSB_PIPE_DESCRIPTOR));

			if (pnum < MsInterface->NumberOfPipes && MsInterface->MsPipes)
			{
				MSUSB_PIPE_DESCRIPTOR* MsPipe = MsInterface->MsPipes[pnum];
				t_MsPipe->MaximumPacketSize = MsPipe->MaximumPacketSize;
				t_MsPipe->MaximumTransferSize = MsPipe->MaximumTransferSize;
				t_MsPipe->PipeFlags = MsPipe->PipeFlags;
			}
			else
			{
				t_MsPipe->MaximumPacketSize = 0;
				t_MsPipe->MaximumTransferSize = 0xffffffff;
				t_MsPipe->PipeFlags = 0;
			}

			t_MsPipe->PipeHandle = 0;
			t_MsPipe->bEndpointAddress = 0;
			t_MsPipe->bInterval = 0;
			t_MsPipe->PipeType = 0;
			t_MsPipe->InitCompleted = 0;
			t_MsPipes[pnum] = t_MsPipe;
		}

		msusb_mspipes_replace(MsInterface, t_MsPipes, LibusbNumEndpoint);
	}

	/* setup configuration */
	MsOutSize = 8;
	/* ConfigurationHandle:  4 bytes
	 * ---------------------------------------------------------------
	 * ||<<< 1 byte >>>|<<< 1 byte >>>|<<<<<<<<<< 2 byte >>>>>>>>>>>||
	 * ||  bus_number  |  dev_number  |      bConfigurationValue    ||
	 * ---------------------------------------------------------------
	 * ***********************/
	MsConfig->ConfigurationHandle = (uint32_t)MsConfig->bConfigurationValue |
	                                ((uint32_t)pdev->bus_number << 24) |
	                                (((uint32_t)pdev->dev_number << 16) & 0xFF0000);
	MsInterfaces = MsConfig->MsInterfaces;

	for (UINT32 inum = 0; inum < MsConfig->NumInterfaces; inum++)
	{
		MsOutSize += 16;
		MSUSB_INTERFACE_DESCRIPTOR* MsInterface = MsInterfaces[inum];
		/* get libusb's interface */
		const LIBUSB_INTERFACE* LibusbInterface =
		    &LibusbConfig->interface[MsInterface->InterfaceNumber];
		const LIBUSB_INTERFACE_DESCRIPTOR* LibusbAltsetting =
		    &LibusbInterface->altsetting[MsInterface->AlternateSetting];
		/* InterfaceHandle:  4 bytes
		 * ---------------------------------------------------------------
		 * ||<<< 1 byte >>>|<<< 1 byte >>>|<<< 1 byte >>>|<<< 1 byte >>>||
		 * ||  bus_number  |  dev_number  |  altsetting  | interfaceNum ||
		 * ---------------------------------------------------------------
		 * ***********************/
		MsInterface->InterfaceHandle =
		    WINPR_ASSERTING_INT_CAST(UINT32, (LibusbAltsetting->bInterfaceNumber |
		                                      (LibusbAltsetting->bAlternateSetting << 8) |
		                                      (pdev->dev_number << 16) | (pdev->bus_number << 24)));
		const size_t len = 16 + (MsInterface->NumberOfPipes * 20);
		MsInterface->Length = WINPR_ASSERTING_INT_CAST(UINT16, len);
		MsInterface->bInterfaceClass = LibusbAltsetting->bInterfaceClass;
		MsInterface->bInterfaceSubClass = LibusbAltsetting->bInterfaceSubClass;
		MsInterface->bInterfaceProtocol = LibusbAltsetting->bInterfaceProtocol;
		MsInterface->InitCompleted = 1;
		MSUSB_PIPE_DESCRIPTOR** MsPipes = MsInterface->MsPipes;
		const BYTE LibusbNumEndpoint = LibusbAltsetting->bNumEndpoints;

		for (UINT32 pnum = 0; pnum < LibusbNumEndpoint; pnum++)
		{
			MsOutSize += 20;

			MSUSB_PIPE_DESCRIPTOR* MsPipe = MsPipes[pnum];
			/* get libusb's endpoint */
			const LIBUSB_ENDPOINT_DESCEIPTOR* LibusbEndpoint = &LibusbAltsetting->endpoint[pnum];
			/* PipeHandle:  4 bytes
			 * ---------------------------------------------------------------
			 * ||<<< 1 byte >>>|<<< 1 byte >>>|<<<<<<<<<< 2 byte >>>>>>>>>>>||
			 * ||  bus_number  |  dev_number  |      bEndpointAddress       ||
			 * ---------------------------------------------------------------
			 * ***********************/
			MsPipe->PipeHandle = LibusbEndpoint->bEndpointAddress |
			                     (((uint32_t)pdev->dev_number << 16) & 0xFF0000) |
			                     (((uint32_t)pdev->bus_number << 24) & 0xFF000000);
			/* count endpoint max packet size */
			unsigned max = LibusbEndpoint->wMaxPacketSize & 0x07ff;
			BYTE attr = LibusbEndpoint->bmAttributes;

			if ((attr & 0x3) == 1 || (attr & 0x3) == 3)
			{
				max *= (1 + ((LibusbEndpoint->wMaxPacketSize >> 11) & 3));
			}

			MsPipe->MaximumPacketSize = WINPR_ASSERTING_INT_CAST(uint16_t, max);
			MsPipe->bEndpointAddress = LibusbEndpoint->bEndpointAddress;
			MsPipe->bInterval = LibusbEndpoint->bInterval;
			MsPipe->PipeType = attr & 0x3;
			MsPipe->InitCompleted = 1;
		}
	}

	MsConfig->MsOutSize = WINPR_ASSERTING_INT_CAST(int, MsOutSize);
	MsConfig->InitCompleted = 1;

	/* replace device's MsConfig */
	if (MsConfig != pdev->MsConfig)
	{
		msusb_msconfig_free(pdev->MsConfig);
		pdev->MsConfig = MsConfig;
	}

	return MsConfig;
}

static int libusb_udev_select_configuration(IUDEVICE* idev, UINT32 bConfigurationValue)
{
	UDEVICE* pdev = (UDEVICE*)idev;
	MSUSB_CONFIG_DESCRIPTOR* MsConfig = nullptr;
	LIBUSB_DEVICE_HANDLE* libusb_handle = nullptr;
	LIBUSB_DEVICE* libusb_dev = nullptr;
	URBDRC_PLUGIN* urbdrc = nullptr;
	LIBUSB_CONFIG_DESCRIPTOR** LibusbConfig = nullptr;
	int ret = 0;

	if (!pdev || !pdev->MsConfig || !pdev->LibusbConfig || !pdev->urbdrc)
		return -1;

	urbdrc = pdev->urbdrc;
	MsConfig = pdev->MsConfig;
	libusb_handle = pdev->libusb_handle;
	libusb_dev = pdev->libusb_dev;
	LibusbConfig = &pdev->LibusbConfig;

	if (MsConfig->InitCompleted)
	{
		func_config_release_all_interface(pdev->urbdrc, libusb_handle,
		                                  (*LibusbConfig)->bNumInterfaces);
	}

	/* The configuration value -1 is mean to put the device in unconfigured state. */
	if (bConfigurationValue == 0)
		ret = libusb_set_configuration(libusb_handle, -1);
	else
		ret = libusb_set_configuration(libusb_handle,
		                               WINPR_ASSERTING_INT_CAST(int, bConfigurationValue));

	if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_set_configuration", ret))
	{
		func_claim_all_interface(urbdrc, libusb_handle, (*LibusbConfig)->bNumInterfaces);
		return -1;
	}
	else
	{
		ret = libusb_get_active_config_descriptor(libusb_dev, LibusbConfig);

		if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_set_configuration", ret))
		{
			func_claim_all_interface(urbdrc, libusb_handle, (*LibusbConfig)->bNumInterfaces);
			return -1;
		}
	}

	func_claim_all_interface(urbdrc, libusb_handle, (*LibusbConfig)->bNumInterfaces);
	return 0;
}

static int libusb_udev_control_pipe_request(IUDEVICE* idev, WINPR_ATTR_UNUSED UINT32 RequestId,
                                            UINT32 EndpointAddress, UINT32* UsbdStatus, int command)
{
	int error = 0;
	UDEVICE* pdev = (UDEVICE*)idev;

	WINPR_ASSERT(EndpointAddress <= UINT8_MAX);
	/*
	pdev->request_queue->register_request(pdev->request_queue, RequestId, nullptr, 0);
	*/
	switch (command)
	{
		case PIPE_CANCEL:
			/** cancel bulk or int transfer */
			idev->cancel_all_transfer_request(idev);
			// dummy_wait_s_obj(1);
			/** set feature to ep (set halt)*/
			/*
			uint8_t request_type, uint8_t bRequest,
			*/
			error = libusb_control_transfer(pdev->libusb_handle,
			                                (uint8_t)LIBUSB_ENDPOINT_OUT |
			                                    (uint8_t)LIBUSB_RECIPIENT_ENDPOINT,
			                                LIBUSB_REQUEST_SET_FEATURE, ENDPOINT_HALT,
			                                (uint16_t)EndpointAddress, nullptr, 0, 1000);
			break;

		case PIPE_RESET:
			idev->cancel_all_transfer_request(idev);
			error = libusb_clear_halt(pdev->libusb_handle, (uint8_t)EndpointAddress);
			// func_set_usbd_status(pdev, UsbdStatus, error);
			break;

		default:
			error = -0xff;
			break;
	}

	*UsbdStatus = 0;
	return error;
}

static UINT32 libusb_udev_control_query_device_text(IUDEVICE* idev, UINT32 TextType,
                                                    UINT16 LocaleId, UINT8* BufferSize,
                                                    BYTE* Buffer)
{
	UDEVICE* pdev = (UDEVICE*)idev;
	LIBUSB_DEVICE_DESCRIPTOR* devDescriptor = nullptr;
	const char strDesc[] = "Generic Usb String";
	char deviceLocation[25] = WINPR_C_ARRAY_INIT;
	BYTE bus_number = 0;
	BYTE device_address = 0;
	int ret = 0;
	size_t len = 0;
	URBDRC_PLUGIN* urbdrc = nullptr;
	WCHAR* text = (WCHAR*)Buffer;
	BYTE slen = 0;
	BYTE locale = 0;
	const UINT8 inSize = *BufferSize;

	*BufferSize = 0;
	if (!pdev || !pdev->devDescriptor || !pdev->urbdrc)
		return ERROR_INVALID_DATA;

	urbdrc = pdev->urbdrc;
	devDescriptor = pdev->devDescriptor;

	switch (TextType)
	{
		case DeviceTextDescription:
		{
			BYTE data[0x100] = WINPR_C_ARRAY_INIT;
			ret = libusb_get_string_descriptor(pdev->libusb_handle, devDescriptor->iProduct,
			                                   LocaleId, data, 0xFF);
			/* The returned data in the buffer is:
			 * 1 byte  length of following data
			 * 1 byte  descriptor type, must be 0x03 for strings
			 * n WCHAR unicode string (of length / 2 characters) including '\0'
			 */
			slen = data[0];
			locale = data[1];

			if ((ret <= 0) || (ret <= 4) || (slen <= 4) || (locale != LIBUSB_DT_STRING) ||
			    (ret > UINT8_MAX))
			{
				const char* msg = "SHORT_DESCRIPTOR";
				if (ret < 0)
					msg = libusb_error_name(ret);
				WLog_Print(urbdrc->log, WLOG_DEBUG,
				           "libusb_get_string_descriptor: "
				           "%s [%d], iProduct: %" PRIu8 "!",
				           msg, ret, devDescriptor->iProduct);

				len = MIN(sizeof(strDesc), inSize);
				for (size_t i = 0; i < len; i++)
					text[i] = (WCHAR)strDesc[i];

				*BufferSize = (BYTE)(len * 2);
			}
			else
			{
				/* ret and slen should be equals, but you never know creativity
				 * of device manufacturers...
				 * So also check the string length returned as server side does
				 * not honor strings with multi '\0' characters well.
				 */
				const size_t rchar = _wcsnlen((WCHAR*)&data[2], sizeof(data) / sizeof(WCHAR));
				len = MIN((BYTE)ret - 2, slen);
				len = MIN(len, inSize);
				len = MIN(len, rchar * sizeof(WCHAR) + sizeof(WCHAR));
				memcpy(Buffer, &data[2], len);

				/* Just as above, the returned WCHAR string should be '\0'
				 * terminated, but never trust hardware to conform to specs... */
				Buffer[len - 2] = '\0';
				Buffer[len - 1] = '\0';
				*BufferSize = (BYTE)len;
			}
		}
		break;

		case DeviceTextLocationInformation:
			bus_number = libusb_get_bus_number(pdev->libusb_dev);
			device_address = libusb_get_device_address(pdev->libusb_dev);
			(void)sprintf_s(deviceLocation, sizeof(deviceLocation),
			                "Port_#%04" PRIu8 ".Hub_#%04" PRIu8 "", device_address, bus_number);

			len = strnlen(deviceLocation,
			              MIN(sizeof(deviceLocation), (inSize > 0) ? inSize - 1U : 0));
			for (size_t i = 0; i < len; i++)
				text[i] = (WCHAR)deviceLocation[i];
			text[len++] = '\0';
			*BufferSize = (UINT8)(len * sizeof(WCHAR));
			break;

		default:
			WLog_Print(urbdrc->log, WLOG_DEBUG, "Query Text: unknown TextType %" PRIu32 "",
			           TextType);
			return ERROR_INVALID_DATA;
	}

	return S_OK;
}

static int libusb_udev_os_feature_descriptor_request(IUDEVICE* idev,
                                                     WINPR_ATTR_UNUSED UINT32 RequestId,
                                                     BYTE Recipient, BYTE InterfaceNumber,
                                                     BYTE Ms_PageIndex, UINT16 Ms_featureDescIndex,
                                                     UINT32* UsbdStatus, UINT32* BufferSize,
                                                     BYTE* Buffer, UINT32 Timeout)
{
	UDEVICE* pdev = (UDEVICE*)idev;
	BYTE ms_string_desc[0x13] = WINPR_C_ARRAY_INIT;
	int error = 0;

	WINPR_ASSERT(idev);
	WINPR_ASSERT(UsbdStatus);
	WINPR_ASSERT(BufferSize);
	WINPR_ASSERT(*BufferSize <= UINT16_MAX);

	/*
	pdev->request_queue->register_request(pdev->request_queue, RequestId, nullptr, 0);
	*/
	error = libusb_control_transfer(pdev->libusb_handle, LIBUSB_ENDPOINT_IN | Recipient,
	                                LIBUSB_REQUEST_GET_DESCRIPTOR, 0x03ee, 0, ms_string_desc, 0x12,
	                                Timeout);

	log_libusb_result(pdev->urbdrc->log, WLOG_DEBUG, "libusb_control_transfer", error);

	if (error > 0)
	{
		const BYTE bMS_Vendorcode = ms_string_desc[16];
		/** get os descriptor */
		error = libusb_control_transfer(
		    pdev->libusb_handle,
		    (uint8_t)LIBUSB_ENDPOINT_IN | (uint8_t)LIBUSB_REQUEST_TYPE_VENDOR | Recipient,
		    bMS_Vendorcode, (UINT16)((InterfaceNumber << 8) | Ms_PageIndex), Ms_featureDescIndex,
		    Buffer, (UINT16)*BufferSize, Timeout);
		log_libusb_result(pdev->urbdrc->log, WLOG_DEBUG, "libusb_control_transfer", error);

		if (error >= 0)
			*BufferSize = (UINT32)error;
	}

	if (error < 0)
		*UsbdStatus = USBD_STATUS_STALL_PID;
	else
		*UsbdStatus = USBD_STATUS_SUCCESS;

	return ERROR_SUCCESS;
}

static int libusb_udev_query_device_descriptor(IUDEVICE* idev, int offset)
{
	UDEVICE* pdev = (UDEVICE*)idev;

	switch (offset)
	{
		case B_LENGTH:
			return pdev->devDescriptor->bLength;

		case B_DESCRIPTOR_TYPE:
			return pdev->devDescriptor->bDescriptorType;

		case BCD_USB:
			return pdev->devDescriptor->bcdUSB;

		case B_DEVICE_CLASS:
			return pdev->devDescriptor->bDeviceClass;

		case B_DEVICE_SUBCLASS:
			return pdev->devDescriptor->bDeviceSubClass;

		case B_DEVICE_PROTOCOL:
			return pdev->devDescriptor->bDeviceProtocol;

		case B_MAX_PACKET_SIZE0:
			return pdev->devDescriptor->bMaxPacketSize0;

		case ID_VENDOR:
			return pdev->devDescriptor->idVendor;

		case ID_PRODUCT:
			return pdev->devDescriptor->idProduct;

		case BCD_DEVICE:
			return pdev->devDescriptor->bcdDevice;

		case I_MANUFACTURER:
			return pdev->devDescriptor->iManufacturer;

		case I_PRODUCT:
			return pdev->devDescriptor->iProduct;

		case I_SERIAL_NUMBER:
			return pdev->devDescriptor->iSerialNumber;

		case B_NUM_CONFIGURATIONS:
			return pdev->devDescriptor->bNumConfigurations;

		default:
			return 0;
	}
}

static BOOL libusb_udev_detach_kernel_driver(IUDEVICE* idev)
{
	int err = 0;
	UDEVICE* pdev = (UDEVICE*)idev;
	URBDRC_PLUGIN* urbdrc = nullptr;

	if (!pdev || !pdev->LibusbConfig || !pdev->libusb_handle || !pdev->urbdrc)
		return FALSE;

#ifdef _WIN32
	return TRUE;
#else
	urbdrc = pdev->urbdrc;

	if ((pdev->status & URBDRC_DEVICE_DETACH_KERNEL) == 0)
	{
		for (int i = 0; i < pdev->LibusbConfig->bNumInterfaces; i++)
		{
			err = libusb_kernel_driver_active(pdev->libusb_handle, i);
			log_libusb_result(urbdrc->log, WLOG_DEBUG, "libusb_kernel_driver_active", err);

			if (err)
			{
				err = libusb_detach_kernel_driver(pdev->libusb_handle, i);
				log_libusb_result(urbdrc->log, WLOG_DEBUG, "libusb_detach_kernel_driver", err);
			}
		}

		pdev->status |= URBDRC_DEVICE_DETACH_KERNEL;
	}

	return TRUE;
#endif
}

static BOOL libusb_udev_attach_kernel_driver(IUDEVICE* idev)
{
	int err = 0;
	UDEVICE* pdev = (UDEVICE*)idev;

	if (!pdev || !pdev->LibusbConfig || !pdev->libusb_handle || !pdev->urbdrc)
		return FALSE;

	for (int i = 0; i < pdev->LibusbConfig->bNumInterfaces && err != LIBUSB_ERROR_NO_DEVICE; i++)
	{
		err = libusb_release_interface(pdev->libusb_handle, i);

		log_libusb_result(pdev->urbdrc->log, WLOG_DEBUG, "libusb_release_interface", err);

#ifndef _WIN32
		if (err != LIBUSB_ERROR_NO_DEVICE)
		{
			err = libusb_attach_kernel_driver(pdev->libusb_handle, i);
			log_libusb_result(pdev->urbdrc->log, WLOG_DEBUG, "libusb_attach_kernel_driver if=%d",
			                  err, i);
		}
#endif
	}

	return TRUE;
}

static int libusb_udev_is_composite_device(IUDEVICE* idev)
{
	UDEVICE* pdev = (UDEVICE*)idev;
	return pdev->isCompositeDevice;
}

static int libusb_udev_is_exist(IUDEVICE* idev)
{
	UDEVICE* pdev = (UDEVICE*)idev;
	return (pdev->status & URBDRC_DEVICE_NOT_FOUND) ? 0 : 1;
}

static int libusb_udev_is_channel_closed(IUDEVICE* idev)
{
	UDEVICE* pdev = (UDEVICE*)idev;
	IUDEVMAN* udevman = nullptr;
	if (!pdev || !pdev->urbdrc)
		return 1;

	udevman = pdev->urbdrc->udevman;
	if (udevman)
	{
		if (udevman->status & URBDRC_DEVICE_CHANNEL_CLOSED)
			return 1;
	}

	if (pdev->status & URBDRC_DEVICE_CHANNEL_CLOSED)
		return 1;

	return 0;
}

static int libusb_udev_is_already_send(IUDEVICE* idev)
{
	UDEVICE* pdev = (UDEVICE*)idev;
	return (pdev->status & URBDRC_DEVICE_ALREADY_SEND) ? 1 : 0;
}

/* This is called from channel cleanup code.
 * Avoid double free, just remove the device and mark the channel closed. */
static void libusb_udev_mark_channel_closed(IUDEVICE* idev)
{
	UDEVICE* pdev = (UDEVICE*)idev;
	if (pdev && ((pdev->status & URBDRC_DEVICE_CHANNEL_CLOSED) == 0))
	{
		URBDRC_PLUGIN* urbdrc = pdev->urbdrc;
		const uint8_t busNr = idev->get_bus_number(idev);
		const uint8_t devNr = idev->get_dev_number(idev);

		pdev->status |= URBDRC_DEVICE_CHANNEL_CLOSED;
		pdev->iface.cancel_all_transfer_request(&pdev->iface);
		if (!urbdrc->udevman->unregister_udevice(urbdrc->udevman, busNr, devNr))
		{
			WLog_Print(pdev->urbdrc->log, WLOG_WARN, "unregister_udevice failed for %d, %d", busNr,
			           devNr);
		}
	}
}

/* This is called by local events where the device is removed or in an error
 * state. Remove the device from redirection and close the channel. */
static void libusb_udev_channel_closed(IUDEVICE* idev)
{
	UDEVICE* pdev = (UDEVICE*)idev;
	if (pdev && ((pdev->status & URBDRC_DEVICE_CHANNEL_CLOSED) == 0))
	{
		URBDRC_PLUGIN* urbdrc = pdev->urbdrc;
		const uint8_t busNr = idev->get_bus_number(idev);
		const uint8_t devNr = idev->get_dev_number(idev);
		IWTSVirtualChannel* channel = nullptr;

		if (pdev->channelManager)
			channel = IFCALLRESULT(nullptr, pdev->channelManager->FindChannelById,
			                       pdev->channelManager, pdev->channelID);

		pdev->status |= URBDRC_DEVICE_CHANNEL_CLOSED;

		if (channel)
		{
			const UINT rc = channel->Write(channel, 0, nullptr, nullptr);
			if (rc != CHANNEL_RC_OK)
				WLog_Print(urbdrc->log, WLOG_WARN, "channel->Write failed with %" PRIu32, rc);
		}

		if (!urbdrc->udevman->unregister_udevice(urbdrc->udevman, busNr, devNr))
			WLog_Print(urbdrc->log, WLOG_WARN, "unregister_udevice failed for %d, %d", busNr,
			           devNr);
	}
}

static void libusb_udev_set_already_send(IUDEVICE* idev)
{
	UDEVICE* pdev = (UDEVICE*)idev;
	pdev->status |= URBDRC_DEVICE_ALREADY_SEND;
}

static char* libusb_udev_get_path(IUDEVICE* idev)
{
	UDEVICE* pdev = (UDEVICE*)idev;
	return pdev->path;
}

static int libusb_udev_query_device_port_status(IUDEVICE* idev, UINT32* UsbdStatus,
                                                UINT32* BufferSize, BYTE* Buffer)
{
	UDEVICE* pdev = (UDEVICE*)idev;
	int success = 0;
	int ret = 0;
	URBDRC_PLUGIN* urbdrc = nullptr;

	if (!pdev || !pdev->urbdrc)
		return -1;

	urbdrc = pdev->urbdrc;

	if (pdev->hub_handle != nullptr)
	{
		ret = idev->control_transfer(
		    idev, 0xffff, 0, 0,
		    (uint8_t)LIBUSB_ENDPOINT_IN | (uint8_t)LIBUSB_REQUEST_TYPE_CLASS |
		        (uint8_t)LIBUSB_RECIPIENT_OTHER,
		    LIBUSB_REQUEST_GET_STATUS, 0, pdev->port_number, UsbdStatus, BufferSize, Buffer, 1000);

		if (log_libusb_result(urbdrc->log, WLOG_DEBUG, "libusb_control_transfer", ret))
			*BufferSize = 0;
		else
		{
			WLog_Print(urbdrc->log, WLOG_DEBUG,
			           "PORT STATUS:0x%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "", Buffer[3],
			           Buffer[2], Buffer[1], Buffer[0]);
			success = 1;
		}
	}

	return success;
}

static int libusb_udev_isoch_transfer(IUDEVICE* idev, GENERIC_CHANNEL_CALLBACK* callback,
                                      UINT32 MessageId, UINT32 RequestId, UINT32 EndpointAddress,
                                      WINPR_ATTR_UNUSED UINT32 TransferFlags, UINT32 StartFrame,
                                      UINT32 ErrorCount, BOOL NoAck,
                                      WINPR_ATTR_UNUSED const BYTE* packetDescriptorData,
                                      UINT32 NumberOfPackets, UINT32 BufferSize, const BYTE* Buffer,
                                      t_isoch_transfer_cb cb, UINT32 Timeout)
{
	int rc = 0;
	UINT32 iso_packet_size = 0;
	UDEVICE* pdev = (UDEVICE*)idev;
	ASYNC_TRANSFER_USER_DATA* user_data = nullptr;
	struct libusb_transfer* iso_transfer = nullptr;
	URBDRC_PLUGIN* urbdrc = nullptr;
	size_t outSize = (12ULL * NumberOfPackets);
	uint32_t streamID = 0x40000000 | RequestId;

	if (!pdev || !pdev->urbdrc)
		return -1;

	urbdrc = pdev->urbdrc;
	user_data = async_transfer_user_data_new(idev, MessageId, 48, BufferSize, Buffer,
	                                         outSize + 1024, NoAck, cb, callback);

	if (!user_data)
		return -1;

	user_data->ErrorCount = ErrorCount;
	user_data->StartFrame = StartFrame;

	if (!Buffer)
		Stream_Seek(user_data->data, (12ULL * NumberOfPackets));

	if (NumberOfPackets > 0)
	{
		iso_packet_size = BufferSize / NumberOfPackets;
		iso_transfer = libusb_alloc_transfer((int)NumberOfPackets);
	}

	if (iso_transfer == nullptr)
	{
		WLog_Print(urbdrc->log, WLOG_ERROR,
		           "Error: libusb_alloc_transfer [NumberOfPackets=%" PRIu32 ", BufferSize=%" PRIu32
		           " ]",
		           NumberOfPackets, BufferSize);
		async_transfer_user_data_free(user_data);
		return -1;
	}

	/**  process URB_FUNCTION_IOSCH_TRANSFER */
	libusb_fill_iso_transfer(
	    iso_transfer, pdev->libusb_handle, WINPR_ASSERTING_INT_CAST(uint8_t, EndpointAddress),
	    Stream_Pointer(user_data->data), WINPR_ASSERTING_INT_CAST(int, BufferSize),
	    WINPR_ASSERTING_INT_CAST(int, NumberOfPackets), func_iso_callback, user_data, Timeout);
	set_stream_id_for_buffer(iso_transfer, streamID);
	libusb_set_iso_packet_lengths(iso_transfer, iso_packet_size);

	if (!ArrayList_Append(pdev->request_queue, iso_transfer))
	{
		WLog_Print(urbdrc->log, WLOG_WARN,
		           "Failed to queue iso transfer, streamID %08" PRIx32 " already in use!",
		           streamID);
		request_free(iso_transfer);
		return -1;
	}
	rc = libusb_submit_transfer(iso_transfer);
	if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_submit_transfer", rc))
		return -1;
	return rc;
}

static BOOL libusb_udev_control_transfer(IUDEVICE* idev, WINPR_ATTR_UNUSED UINT32 RequestId,
                                         WINPR_ATTR_UNUSED UINT32 EndpointAddress,
                                         WINPR_ATTR_UNUSED UINT32 TransferFlags, BYTE bmRequestType,
                                         BYTE Request, UINT16 Value, UINT16 Index,
                                         UINT32* UrbdStatus, UINT32* BufferSize, BYTE* Buffer,
                                         UINT32 Timeout)
{
	int status = 0;
	UDEVICE* pdev = (UDEVICE*)idev;

	WINPR_ASSERT(BufferSize);
	WINPR_ASSERT(*BufferSize <= UINT16_MAX);

	if (!pdev || !pdev->urbdrc)
		return FALSE;

	status = libusb_control_transfer(pdev->libusb_handle, bmRequestType, Request, Value, Index,
	                                 Buffer, (UINT16)*BufferSize, Timeout);

	if (status >= 0)
		*BufferSize = (UINT32)status;
	else
		log_libusb_result(pdev->urbdrc->log, WLOG_ERROR, "libusb_control_transfer", status);

	if (!func_set_usbd_status(pdev->urbdrc, pdev, UrbdStatus, status))
		return FALSE;

	return TRUE;
}

static int libusb_udev_bulk_or_interrupt_transfer(IUDEVICE* idev,
                                                  GENERIC_CHANNEL_CALLBACK* callback,
                                                  UINT32 MessageId, UINT32 RequestId,
                                                  UINT32 EndpointAddress, UINT32 TransferFlags,
                                                  BOOL NoAck, UINT32 BufferSize, const BYTE* data,
                                                  t_isoch_transfer_cb cb, UINT32 Timeout)
{
	int rc = 0;
	UINT32 transfer_type = 0;
	UDEVICE* pdev = (UDEVICE*)idev;
	const LIBUSB_ENDPOINT_DESCEIPTOR* ep_desc = nullptr;
	struct libusb_transfer* transfer = nullptr;
	URBDRC_PLUGIN* urbdrc = nullptr;
	ASYNC_TRANSFER_USER_DATA* user_data = nullptr;
	uint32_t streamID = 0x80000000 | RequestId;

	if (!pdev || !pdev->LibusbConfig || !pdev->urbdrc)
		return -1;

	urbdrc = pdev->urbdrc;
	user_data =
	    async_transfer_user_data_new(idev, MessageId, 36, BufferSize, data, 0, NoAck, cb, callback);

	if (!user_data)
		return -1;

	/* alloc memory for urb transfer */
	transfer = libusb_alloc_transfer(0);
	if (!transfer)
	{
		async_transfer_user_data_free(user_data);
		return -1;
	}
	transfer->user_data = user_data;

	ep_desc = func_get_ep_desc(pdev->LibusbConfig, pdev->MsConfig, EndpointAddress);

	if (!ep_desc)
	{
		WLog_Print(urbdrc->log, WLOG_ERROR, "func_get_ep_desc: endpoint 0x%" PRIx32 " not found",
		           EndpointAddress);
		request_free(transfer);
		return -1;
	}

	transfer_type = (ep_desc->bmAttributes) & 0x3;
	WLog_Print(urbdrc->log, WLOG_DEBUG,
	           "urb_bulk_or_interrupt_transfer: ep:0x%" PRIx32 " "
	           "transfer_type %" PRIu32 " flag:%" PRIu32 " OutputBufferSize:0x%" PRIx32 "",
	           EndpointAddress, transfer_type, TransferFlags, BufferSize);

	switch (transfer_type)
	{
		case BULK_TRANSFER:
			/** Bulk Transfer */
			libusb_fill_bulk_transfer(
			    transfer, pdev->libusb_handle, WINPR_ASSERTING_INT_CAST(uint8_t, EndpointAddress),
			    Stream_Pointer(user_data->data), WINPR_ASSERTING_INT_CAST(int, BufferSize),
			    func_bulk_transfer_cb, user_data, Timeout);
			break;

		case INTERRUPT_TRANSFER:
			/**  Interrupt Transfer */
			libusb_fill_interrupt_transfer(
			    transfer, pdev->libusb_handle, WINPR_ASSERTING_INT_CAST(uint8_t, EndpointAddress),
			    Stream_Pointer(user_data->data), WINPR_ASSERTING_INT_CAST(int, BufferSize),
			    func_bulk_transfer_cb, user_data, Timeout);
			break;

		default:
			WLog_Print(urbdrc->log, WLOG_DEBUG,
			           "urb_bulk_or_interrupt_transfer:"
			           " other transfer type 0x%" PRIX32 "",
			           transfer_type);
			request_free(transfer);
			return -1;
	}

	set_stream_id_for_buffer(transfer, streamID);

	if (!ArrayList_Append(pdev->request_queue, transfer))
	{
		WLog_Print(urbdrc->log, WLOG_WARN,
		           "Failed to queue transfer, streamID %08" PRIx32 " already in use!", streamID);
		request_free(transfer);
		return -1;
	}
	rc = libusb_submit_transfer(transfer);
	if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_submit_transfer", rc))
		return -1;
	return rc;
}

static int func_cancel_xact_request(URBDRC_PLUGIN* urbdrc, struct libusb_transfer* transfer)
{
	int status = 0;

	if (!urbdrc || !transfer)
		return -1;

	status = libusb_cancel_transfer(transfer);

	if (log_libusb_result(urbdrc->log, WLOG_WARN, "libusb_cancel_transfer", status))
	{
		if (status == LIBUSB_ERROR_NOT_FOUND)
			return -1;
	}
	else
		return 1;

	return 0;
}

static void libusb_udev_cancel_all_transfer_request(IUDEVICE* idev)
{
	UDEVICE* pdev = (UDEVICE*)idev;
	size_t count = 0;

	if (!pdev || !pdev->request_queue || !pdev->urbdrc)
		return;

	ArrayList_Lock(pdev->request_queue);
	count = ArrayList_Count(pdev->request_queue);

	for (size_t x = 0; x < count; x++)
	{
		struct libusb_transfer* transfer = ArrayList_GetItem(pdev->request_queue, x);
		func_cancel_xact_request(pdev->urbdrc, transfer);
	}

	ArrayList_Unlock(pdev->request_queue);
}

static int libusb_udev_cancel_transfer_request(IUDEVICE* idev, UINT32 RequestId)
{
	int rc = -1;
	UDEVICE* pdev = (UDEVICE*)idev;
	struct libusb_transfer* transfer = nullptr;
	uint32_t cancelID1 = 0x40000000 | RequestId;
	uint32_t cancelID2 = 0x80000000 | RequestId;

	if (!idev || !pdev->urbdrc || !pdev->request_queue)
		return -1;

	ArrayList_Lock(pdev->request_queue);
	transfer = list_contains(pdev->request_queue, cancelID1);
	if (!transfer)
		transfer = list_contains(pdev->request_queue, cancelID2);

	if (transfer)
	{
		URBDRC_PLUGIN* urbdrc = pdev->urbdrc;

		rc = func_cancel_xact_request(urbdrc, transfer);
	}
	ArrayList_Unlock(pdev->request_queue);
	return rc;
}

BASIC_STATE_FUNC_DEFINED(channelManager, IWTSVirtualChannelManager*)
BASIC_STATE_FUNC_DEFINED(channelID, UINT32)
BASIC_STATE_FUNC_DEFINED(ReqCompletion, UINT32)
BASIC_STATE_FUNC_DEFINED(bus_number, BYTE)
BASIC_STATE_FUNC_DEFINED(dev_number, BYTE)
BASIC_STATE_FUNC_DEFINED(port_number, UINT8)
BASIC_STATE_FUNC_DEFINED(MsConfig, MSUSB_CONFIG_DESCRIPTOR*)

BASIC_POINT_FUNC_DEFINED(udev, void*)
BASIC_POINT_FUNC_DEFINED(prev, void*)
BASIC_POINT_FUNC_DEFINED(next, void*)

static UINT32 udev_get_UsbDevice(IUDEVICE* idev)
{
	UDEVICE* pdev = (UDEVICE*)idev;

	if (!pdev)
		return 0;

	return pdev->UsbDevice;
}

static void udev_set_UsbDevice(IUDEVICE* idev, UINT32 val)
{
	UDEVICE* pdev = (UDEVICE*)idev;

	if (!pdev)
		return;

	pdev->UsbDevice = val;
}

static void udev_free(IUDEVICE* idev)
{
	int rc = 0;
	UDEVICE* udev = (UDEVICE*)idev;
	URBDRC_PLUGIN* urbdrc = nullptr;

	if (!idev || !udev->urbdrc)
		return;

	urbdrc = udev->urbdrc;

	libusb_udev_cancel_all_transfer_request(&udev->iface);
	if (udev->libusb_handle)
	{
		rc = libusb_reset_device(udev->libusb_handle);

		log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_reset_device", rc);
	}

	/* HACK: We need to wait until the cancel transfer has been processed by
	 * poll_libusb_events
	 */
	Sleep(100);

	/* release all interface and  attach kernel driver */
	if (!udev->iface.attach_kernel_driver(idev))
		WLog_Print(udev->urbdrc->log, WLOG_WARN, "attach_kernel_driver failed for device");
	ArrayList_Free(udev->request_queue);
	/* free the config descriptor that send from windows */
	msusb_msconfig_free(udev->MsConfig);
	libusb_unref_device(udev->libusb_dev);
	libusb_close(udev->libusb_handle);
	libusb_close(udev->hub_handle);
	free(udev->devDescriptor);
	free(idev);
}

static void udev_load_interface(UDEVICE* pdev)
{
	WINPR_ASSERT(pdev);

	/* load interface */
	/* Basic */
	BASIC_STATE_FUNC_REGISTER(channelManager, pdev);
	BASIC_STATE_FUNC_REGISTER(channelID, pdev);
	BASIC_STATE_FUNC_REGISTER(UsbDevice, pdev);
	BASIC_STATE_FUNC_REGISTER(ReqCompletion, pdev);
	BASIC_STATE_FUNC_REGISTER(bus_number, pdev);
	BASIC_STATE_FUNC_REGISTER(dev_number, pdev);
	BASIC_STATE_FUNC_REGISTER(port_number, pdev);
	BASIC_STATE_FUNC_REGISTER(MsConfig, pdev);
	BASIC_STATE_FUNC_REGISTER(p_udev, pdev);
	BASIC_STATE_FUNC_REGISTER(p_prev, pdev);
	BASIC_STATE_FUNC_REGISTER(p_next, pdev);
	pdev->iface.isCompositeDevice = libusb_udev_is_composite_device;
	pdev->iface.isExist = libusb_udev_is_exist;
	pdev->iface.isAlreadySend = libusb_udev_is_already_send;
	pdev->iface.isChannelClosed = libusb_udev_is_channel_closed;
	pdev->iface.setAlreadySend = libusb_udev_set_already_send;
	pdev->iface.setChannelClosed = libusb_udev_channel_closed;
	pdev->iface.markChannelClosed = libusb_udev_mark_channel_closed;
	pdev->iface.getPath = libusb_udev_get_path;
	/* Transfer */
	pdev->iface.isoch_transfer = libusb_udev_isoch_transfer;
	pdev->iface.control_transfer = libusb_udev_control_transfer;
	pdev->iface.bulk_or_interrupt_transfer = libusb_udev_bulk_or_interrupt_transfer;
	pdev->iface.select_interface = libusb_udev_select_interface;
	pdev->iface.select_configuration = libusb_udev_select_configuration;
	pdev->iface.complete_msconfig_setup = libusb_udev_complete_msconfig_setup;
	pdev->iface.control_pipe_request = libusb_udev_control_pipe_request;
	pdev->iface.control_query_device_text = libusb_udev_control_query_device_text;
	pdev->iface.os_feature_descriptor_request = libusb_udev_os_feature_descriptor_request;
	pdev->iface.cancel_all_transfer_request = libusb_udev_cancel_all_transfer_request;
	pdev->iface.cancel_transfer_request = libusb_udev_cancel_transfer_request;
	pdev->iface.query_device_descriptor = libusb_udev_query_device_descriptor;
	pdev->iface.detach_kernel_driver = libusb_udev_detach_kernel_driver;
	pdev->iface.attach_kernel_driver = libusb_udev_attach_kernel_driver;
	pdev->iface.query_device_port_status = libusb_udev_query_device_port_status;
	pdev->iface.free = udev_free;
}

static int udev_get_device_handle(URBDRC_PLUGIN* urbdrc, libusb_context* ctx, UDEVICE* pdev,
                                  UINT16 bus_number, UINT16 dev_number)
{
	int error = -1;
	uint8_t port_numbers[16] = WINPR_C_ARRAY_INIT;
	LIBUSB_DEVICE** libusb_list = nullptr;
	const ssize_t total_device = libusb_get_device_list(ctx, &libusb_list);

	WINPR_ASSERT(urbdrc);

	/* Look for device. */
	for (ssize_t i = 0; i < total_device; i++)
	{
		LIBUSB_DEVICE* dev = libusb_list[i];

		if ((bus_number != libusb_get_bus_number(dev)) ||
		    (dev_number != libusb_get_device_address(dev)))
			libusb_unref_device(dev);
		else
		{
			error = libusb_open(dev, &pdev->libusb_handle);

			if (log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_open", error))
			{
				libusb_unref_device(dev);
				continue;
			}

			/* get port number */
			error = libusb_get_port_numbers(dev, port_numbers, sizeof(port_numbers));
			if (error < 1)
			{
				/* Prevent open hub, treat as error. */
				log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_get_port_numbers", error);
				libusb_unref_device(dev);
				continue;
			}

			pdev->port_number = port_numbers[(error - 1)];
			error = 0;
			WLog_Print(urbdrc->log, WLOG_DEBUG, "  Port: %" PRIu8, pdev->port_number);
			/* gen device path */
			(void)_snprintf(pdev->path, sizeof(pdev->path), "%" PRIu16 "-%d", bus_number,
			                pdev->port_number);

			WLog_Print(urbdrc->log, WLOG_DEBUG, "  DevPath: %s", pdev->path);
		}
	}
	libusb_free_device_list(libusb_list, 0);

	if (error < 0)
		return -1;
	return 0;
}

static int udev_get_hub_handle(URBDRC_PLUGIN* urbdrc, libusb_context* ctx, UDEVICE* pdev,
                               UINT16 bus_number, WINPR_ATTR_UNUSED UINT16 dev_number)
{
	int error = -1;
	LIBUSB_DEVICE** libusb_list = nullptr;
	LIBUSB_DEVICE_HANDLE* handle = nullptr;
	const ssize_t total_device = libusb_get_device_list(ctx, &libusb_list);

	WINPR_ASSERT(urbdrc);

	/* Look for device hub. */
	for (ssize_t i = 0; i < total_device; i++)
	{
		LIBUSB_DEVICE* dev = libusb_list[i];

		if ((bus_number != libusb_get_bus_number(dev)) ||
		    (1 != libusb_get_device_address(dev))) /* Root hub always first on bus. */
			libusb_unref_device(dev);
		else
		{
			WLog_Print(urbdrc->log, WLOG_DEBUG, "  Open hub: %" PRIu16 "", bus_number);
			error = libusb_open(dev, &handle);

			if (!log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_open", error))
				pdev->hub_handle = handle;
			else
				libusb_unref_device(dev);
		}
	}

	libusb_free_device_list(libusb_list, 0);

	if (error < 0)
		return -1;

	return 0;
}

static void request_free(void* value)
{
	ASYNC_TRANSFER_USER_DATA* user_data = nullptr;
	struct libusb_transfer* transfer = (struct libusb_transfer*)value;
	if (!transfer)
		return;

	user_data = (ASYNC_TRANSFER_USER_DATA*)transfer->user_data;
	async_transfer_user_data_free(user_data);
	transfer->user_data = nullptr;
	libusb_free_transfer(transfer);
}

static IUDEVICE* udev_init(URBDRC_PLUGIN* urbdrc, libusb_context* context, LIBUSB_DEVICE* device,
                           BYTE bus_number, BYTE dev_number)
{
	UDEVICE* pdev = nullptr;
	int status = LIBUSB_ERROR_OTHER;
	LIBUSB_DEVICE_DESCRIPTOR* devDescriptor = nullptr;
	LIBUSB_CONFIG_DESCRIPTOR* config_temp = nullptr;
	LIBUSB_INTERFACE_DESCRIPTOR interface_temp;

	WINPR_ASSERT(urbdrc);

	pdev = (PUDEVICE)calloc(1, sizeof(UDEVICE));

	if (!pdev)
		return nullptr;

	pdev->urbdrc = urbdrc;
	udev_load_interface(pdev);

	if (device)
		pdev->libusb_dev = device;
	else
		pdev->libusb_dev = udev_get_libusb_dev(context, bus_number, dev_number);

	if (pdev->libusb_dev == nullptr)
		goto fail;

	if (urbdrc->listener_callback)
		udev_set_channelManager(&pdev->iface, urbdrc->listener_callback->channel_mgr);

	/* Get DEVICE handle */
	status = udev_get_device_handle(urbdrc, context, pdev, bus_number, dev_number);
	if (status != LIBUSB_SUCCESS)
	{
		struct libusb_device_descriptor desc;
		const uint8_t port = libusb_get_port_number(pdev->libusb_dev);
		libusb_get_device_descriptor(pdev->libusb_dev, &desc);

		log_libusb_result(urbdrc->log, WLOG_ERROR,
		                  "libusb_open [b=0x%02X,p=0x%02X,a=0x%02X,VID=0x%04X,PID=0x%04X]", status,
		                  bus_number, port, dev_number, desc.idVendor, desc.idProduct);
		goto fail;
	}

	/* Get HUB handle */
	status = udev_get_hub_handle(urbdrc, context, pdev, bus_number, dev_number);

	if (status < 0)
		pdev->hub_handle = nullptr;

	pdev->devDescriptor = udev_new_descript(urbdrc, pdev->libusb_dev);

	if (!pdev->devDescriptor)
		goto fail;

	status = libusb_get_active_config_descriptor(pdev->libusb_dev, &pdev->LibusbConfig);

	if (status == LIBUSB_ERROR_NOT_FOUND)
		status = libusb_get_config_descriptor(pdev->libusb_dev, 0, &pdev->LibusbConfig);

	if (status < 0)
	{
		log_libusb_result(urbdrc->log, WLOG_ERROR, "libusb_get_config_descriptor", status);
		goto fail;
	}

	config_temp = pdev->LibusbConfig;
	/* get the first interface and first altsetting */
	interface_temp = config_temp->interface[0].altsetting[0];
	WLog_Print(urbdrc->log, WLOG_DEBUG,
	           "Registered Device: Vid: 0x%04" PRIX16 " Pid: 0x%04" PRIX16 ""
	           " InterfaceClass = %s",
	           pdev->devDescriptor->idVendor, pdev->devDescriptor->idProduct,
	           usb_interface_class_to_string(interface_temp.bInterfaceClass));
	/* Check composite device */
	devDescriptor = pdev->devDescriptor;

	if ((devDescriptor->bNumConfigurations == 1) && (config_temp->bNumInterfaces > 1) &&
	    (devDescriptor->bDeviceClass == LIBUSB_CLASS_PER_INTERFACE))
	{
		pdev->isCompositeDevice = 1;
	}
	else if ((devDescriptor->bDeviceClass == 0xef) &&
	         (devDescriptor->bDeviceSubClass == LIBUSB_CLASS_COMM) &&
	         (devDescriptor->bDeviceProtocol == 0x01))
	{
		pdev->isCompositeDevice = 1;
	}
	else
		pdev->isCompositeDevice = 0;

	/* set device class to first interface class */
	devDescriptor->bDeviceClass = interface_temp.bInterfaceClass;
	devDescriptor->bDeviceSubClass = interface_temp.bInterfaceSubClass;
	devDescriptor->bDeviceProtocol = interface_temp.bInterfaceProtocol;
	/* initialize pdev */
	pdev->bus_number = bus_number;
	pdev->dev_number = dev_number;
	pdev->request_queue = ArrayList_New(TRUE);

	if (!pdev->request_queue)
		goto fail;

	ArrayList_Object(pdev->request_queue)->fnObjectFree = request_free;

	/* set config of windows */
	pdev->MsConfig = msusb_msconfig_new();

	if (!pdev->MsConfig)
		goto fail;

	// deb_config_msg(pdev->libusb_dev, config_temp, devDescriptor->bNumConfigurations);
	return &pdev->iface;
fail:
	pdev->iface.free(&pdev->iface);
	return nullptr;
}

size_t udev_new_by_id(URBDRC_PLUGIN* urbdrc, libusb_context* ctx, UINT16 idVendor, UINT16 idProduct,
                      IUDEVICE*** devArray)
{
	WINPR_ASSERT(urbdrc);
	WINPR_ASSERT(devArray);

	size_t num = 0;
	LIBUSB_DEVICE** libusb_list = nullptr;

	*devArray = nullptr;

	WLog_Print(urbdrc->log, WLOG_INFO, "VID: 0x%04" PRIX16 ", PID: 0x%04" PRIX16 "", idVendor,
	           idProduct);
	const ssize_t total_device = libusb_get_device_list(ctx, &libusb_list);
	if (total_device < 0)
	{
		WLog_Print(urbdrc->log, WLOG_ERROR, "libusb_get_device_list -> [%" PRIdz "]", total_device);
		return 0;
	}
	if (total_device == 0)
	{
		WLog_Print(urbdrc->log, WLOG_WARN, "libusb_get_device_list -> [%" PRIdz "]", total_device);
		return 0;
	}

	UDEVICE** array = (UDEVICE**)calloc((size_t)total_device, sizeof(UDEVICE*));

	if (!array)
		goto fail;

	for (ssize_t i = 0; i < total_device; i++)
	{
		LIBUSB_DEVICE* dev = libusb_list[i];
		LIBUSB_DEVICE_DESCRIPTOR* descriptor = udev_new_descript(urbdrc, dev);

		if ((descriptor->idVendor == idVendor) && (descriptor->idProduct == idProduct))
		{
			const uint8_t nr = libusb_get_bus_number(dev);
			const uint8_t addr = libusb_get_device_address(dev);
			array[num] = (PUDEVICE)udev_init(urbdrc, ctx, dev, nr, addr);

			if (array[num] != nullptr)
				num++;
			else
			{
				WLog_Print(urbdrc->log, WLOG_WARN,
				           "udev_init(nr=%" PRIu8 ", addr=%" PRIu8 ") failed", nr, addr);
			}
		}
		else
			libusb_unref_device(dev);

		free(descriptor);
	}

fail:
	libusb_free_device_list(libusb_list, 0);
	*devArray = (IUDEVICE**)array;
	return num;
}

IUDEVICE* udev_new_by_addr(URBDRC_PLUGIN* urbdrc, libusb_context* context, BYTE bus_number,
                           BYTE dev_number)
{
	WLog_Print(urbdrc->log, WLOG_DEBUG, "bus:%d dev:%d", bus_number, dev_number);
	return udev_init(urbdrc, context, nullptr, bus_number, dev_number);
}
