/**
 * FreeRDP: A Remote Desktop Protocol Implementation
 * Server USB redirection channel - helper functions
 *
 * Copyright 2019 Armin Novak <armin.novak@thincast.com>
 * Copyright 2019 Thincast Technologies GmbH
 *
 * 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 <freerdp/config.h>

#include "urbdrc_helpers.h"
#include "urbdrc_types.h"
#include <winpr/print.h>

const char* mask_to_string(UINT32 mask)
{
	switch (mask)
	{
		case STREAM_ID_NONE:
			return "STREAM_ID_NONE";

		case STREAM_ID_PROXY:
			return "STREAM_ID_PROXY";

		case STREAM_ID_STUB:
			return "STREAM_ID_STUB";

		default:
			return "UNKNOWN";
	}
}
const char* interface_to_string(UINT32 id)
{
	switch (id)
	{
		case CAPABILITIES_NEGOTIATOR:
			return "CAPABILITIES_NEGOTIATOR";

		case SERVER_CHANNEL_NOTIFICATION:
			return "SERVER_CHANNEL_NOTIFICATION";

		case CLIENT_CHANNEL_NOTIFICATION:
			return "CLIENT_CHANNEL_NOTIFICATION";

		default:
			return "DEVICE_MESSAGE";
	}
}

static const char* call_to_string_none(BOOL client, UINT32 interfaceId, UINT32 functionId)
{
	WINPR_UNUSED(interfaceId);

	if (client)
		return "RIM_EXCHANGE_CAPABILITY_RESPONSE  [none |client]";
	else
	{
		switch (functionId)
		{
			case RIM_EXCHANGE_CAPABILITY_REQUEST:
				return "RIM_EXCHANGE_CAPABILITY_REQUEST   [none |server]";

			case RIMCALL_RELEASE:
				return "RIMCALL_RELEASE                   [none |server]";

			case RIMCALL_QUERYINTERFACE:
				return "RIMCALL_QUERYINTERFACE            [none |server]";

			default:
				return "UNKNOWN                           [none |server]";
		}
	}
}

static const char* call_to_string_proxy_server(UINT32 functionId)
{
	switch (functionId)
	{
		case QUERY_DEVICE_TEXT:
			return "QUERY_DEVICE_TEXT                 [proxy|server]";

		case INTERNAL_IO_CONTROL:
			return "INTERNAL_IO_CONTROL               [proxy|server]";

		case IO_CONTROL:
			return "IO_CONTROL                        [proxy|server]";

		case REGISTER_REQUEST_CALLBACK:
			return "REGISTER_REQUEST_CALLBACK         [proxy|server]";

		case CANCEL_REQUEST:
			return "CANCEL_REQUEST                    [proxy|server]";

		case RETRACT_DEVICE:
			return "RETRACT_DEVICE                    [proxy|server]";

		case TRANSFER_IN_REQUEST:
			return "TRANSFER_IN_REQUEST               [proxy|server]";

		case TRANSFER_OUT_REQUEST:
			return "TRANSFER_OUT_REQUEST              [proxy|server]";

		default:
			return "UNKNOWN                           [proxy|server]";
	}
}

static const char* call_to_string_proxy_client(UINT32 functionId)
{
	switch (functionId)
	{
		case URB_COMPLETION_NO_DATA:
			return "URB_COMPLETION_NO_DATA            [proxy|client]";

		case URB_COMPLETION:
			return "URB_COMPLETION                    [proxy|client]";

		case IOCONTROL_COMPLETION:
			return "IOCONTROL_COMPLETION              [proxy|client]";

		case TRANSFER_OUT_REQUEST:
			return "TRANSFER_OUT_REQUEST              [proxy|client]";

		default:
			return "UNKNOWN                           [proxy|client]";
	}
}

static const char* call_to_string_proxy(BOOL client, UINT32 interfaceId, UINT32 functionId)
{
	switch (interfaceId & INTERFACE_ID_MASK)
	{
		case CLIENT_DEVICE_SINK:
			switch (functionId)
			{
				case ADD_VIRTUAL_CHANNEL:
					return "ADD_VIRTUAL_CHANNEL               [proxy|sink  ]";

				case ADD_DEVICE:
					return "ADD_DEVICE                        [proxy|sink  ]";
				case RIMCALL_RELEASE:
					return "RIMCALL_RELEASE                   [proxy|sink  ]";

				case RIMCALL_QUERYINTERFACE:
					return "RIMCALL_QUERYINTERFACE            [proxy|sink  ]";
				default:
					return "UNKNOWN                           [proxy|sink  ]";
			}

		case SERVER_CHANNEL_NOTIFICATION:
			switch (functionId)
			{
				case CHANNEL_CREATED:
					return "CHANNEL_CREATED                   [proxy|server]";

				case RIMCALL_RELEASE:
					return "RIMCALL_RELEASE                   [proxy|server]";

				case RIMCALL_QUERYINTERFACE:
					return "RIMCALL_QUERYINTERFACE            [proxy|server]";

				default:
					return "UNKNOWN                           [proxy|server]";
			}

		case CLIENT_CHANNEL_NOTIFICATION:
			switch (functionId)
			{
				case CHANNEL_CREATED:
					return "CHANNEL_CREATED                   [proxy|client]";
				case RIMCALL_RELEASE:
					return "RIMCALL_RELEASE                   [proxy|client]";
				case RIMCALL_QUERYINTERFACE:
					return "RIMCALL_QUERYINTERFACE            [proxy|client]";
				default:
					return "UNKNOWN                           [proxy|client]";
			}

		default:
			if (client)
				return call_to_string_proxy_client(functionId);
			else
				return call_to_string_proxy_server(functionId);
	}
}

static const char* call_to_string_stub(WINPR_ATTR_UNUSED BOOL client,
                                       WINPR_ATTR_UNUSED UINT32 interfaceNr,
                                       WINPR_ATTR_UNUSED UINT32 functionId)
{
	return "QUERY_DEVICE_TEXT_RSP             [stub  |client]";
}

const char* call_to_string(BOOL client, UINT32 interfaceNr, UINT32 functionId)
{
	const UINT32 mask = (interfaceNr & STREAM_ID_MASK) >> 30;
	const UINT32 interfaceId = interfaceNr & INTERFACE_ID_MASK;

	switch (mask)
	{
		case STREAM_ID_NONE:
			return call_to_string_none(client, interfaceId, functionId);

		case STREAM_ID_PROXY:
			return call_to_string_proxy(client, interfaceId, functionId);

		case STREAM_ID_STUB:
			return call_to_string_stub(client, interfaceId, functionId);

		default:
			return "UNKNOWN[mask]";
	}
}

const char* urb_function_string(UINT16 urb)
{
	switch (urb)
	{
		case TS_URB_SELECT_CONFIGURATION:
			return "TS_URB_SELECT_CONFIGURATION";

		case TS_URB_SELECT_INTERFACE:
			return "TS_URB_SELECT_INTERFACE";

		case TS_URB_PIPE_REQUEST:
			return "TS_URB_PIPE_REQUEST";

		case TS_URB_TAKE_FRAME_LENGTH_CONTROL:
			return "TS_URB_TAKE_FRAME_LENGTH_CONTROL";

		case TS_URB_RELEASE_FRAME_LENGTH_CONTROL:
			return "TS_URB_RELEASE_FRAME_LENGTH_CONTROL";

		case TS_URB_GET_FRAME_LENGTH:
			return "TS_URB_GET_FRAME_LENGTH";

		case TS_URB_SET_FRAME_LENGTH:
			return "TS_URB_SET_FRAME_LENGTH";

		case TS_URB_GET_CURRENT_FRAME_NUMBER:
			return "TS_URB_GET_CURRENT_FRAME_NUMBER";

		case TS_URB_CONTROL_TRANSFER:
			return "TS_URB_CONTROL_TRANSFER";

		case TS_URB_BULK_OR_INTERRUPT_TRANSFER:
			return "TS_URB_BULK_OR_INTERRUPT_TRANSFER";

		case TS_URB_ISOCH_TRANSFER:
			return "TS_URB_ISOCH_TRANSFER";

		case TS_URB_GET_DESCRIPTOR_FROM_DEVICE:
			return "TS_URB_GET_DESCRIPTOR_FROM_DEVICE";

		case TS_URB_SET_DESCRIPTOR_TO_DEVICE:
			return "TS_URB_SET_DESCRIPTOR_TO_DEVICE";

		case TS_URB_SET_FEATURE_TO_DEVICE:
			return "TS_URB_SET_FEATURE_TO_DEVICE";

		case TS_URB_SET_FEATURE_TO_INTERFACE:
			return "TS_URB_SET_FEATURE_TO_INTERFACE";

		case TS_URB_SET_FEATURE_TO_ENDPOINT:
			return "TS_URB_SET_FEATURE_TO_ENDPOINT";

		case TS_URB_CLEAR_FEATURE_TO_DEVICE:
			return "TS_URB_CLEAR_FEATURE_TO_DEVICE";

		case TS_URB_CLEAR_FEATURE_TO_INTERFACE:
			return "TS_URB_CLEAR_FEATURE_TO_INTERFACE";

		case TS_URB_CLEAR_FEATURE_TO_ENDPOINT:
			return "TS_URB_CLEAR_FEATURE_TO_ENDPOINT";

		case TS_URB_GET_STATUS_FROM_DEVICE:
			return "TS_URB_GET_STATUS_FROM_DEVICE";

		case TS_URB_GET_STATUS_FROM_INTERFACE:
			return "TS_URB_GET_STATUS_FROM_INTERFACE";

		case TS_URB_GET_STATUS_FROM_ENDPOINT:
			return "TS_URB_GET_STATUS_FROM_ENDPOINT";

		case TS_URB_RESERVED_0X0016:
			return "TS_URB_RESERVED_0X0016";

		case TS_URB_VENDOR_DEVICE:
			return "TS_URB_VENDOR_DEVICE";

		case TS_URB_VENDOR_INTERFACE:
			return "TS_URB_VENDOR_INTERFACE";

		case TS_URB_VENDOR_ENDPOINT:
			return "TS_URB_VENDOR_ENDPOINT";

		case TS_URB_CLASS_DEVICE:
			return "TS_URB_CLASS_DEVICE";

		case TS_URB_CLASS_INTERFACE:
			return "TS_URB_CLASS_INTERFACE";

		case TS_URB_CLASS_ENDPOINT:
			return "TS_URB_CLASS_ENDPOINT";

		case TS_URB_RESERVE_0X001D:
			return "TS_URB_RESERVE_0X001D";

		case TS_URB_SYNC_RESET_PIPE_AND_CLEAR_STALL:
			return "TS_URB_SYNC_RESET_PIPE_AND_CLEAR_STALL";

		case TS_URB_CLASS_OTHER:
			return "TS_URB_CLASS_OTHER";

		case TS_URB_VENDOR_OTHER:
			return "TS_URB_VENDOR_OTHER";

		case TS_URB_GET_STATUS_FROM_OTHER:
			return "TS_URB_GET_STATUS_FROM_OTHER";

		case TS_URB_CLEAR_FEATURE_TO_OTHER:
			return "TS_URB_CLEAR_FEATURE_TO_OTHER";

		case TS_URB_SET_FEATURE_TO_OTHER:
			return "TS_URB_SET_FEATURE_TO_OTHER";

		case TS_URB_GET_DESCRIPTOR_FROM_ENDPOINT:
			return "TS_URB_GET_DESCRIPTOR_FROM_ENDPOINT";

		case TS_URB_SET_DESCRIPTOR_TO_ENDPOINT:
			return "TS_URB_SET_DESCRIPTOR_TO_ENDPOINT";

		case TS_URB_CONTROL_GET_CONFIGURATION_REQUEST:
			return "TS_URB_CONTROL_GET_CONFIGURATION_REQUEST";

		case TS_URB_CONTROL_GET_INTERFACE_REQUEST:
			return "TS_URB_CONTROL_GET_INTERFACE_REQUEST";

		case TS_URB_GET_DESCRIPTOR_FROM_INTERFACE:
			return "TS_URB_GET_DESCRIPTOR_FROM_INTERFACE";

		case TS_URB_SET_DESCRIPTOR_TO_INTERFACE:
			return "TS_URB_SET_DESCRIPTOR_TO_INTERFACE";

		case TS_URB_GET_OS_FEATURE_DESCRIPTOR_REQUEST:
			return "TS_URB_GET_OS_FEATURE_DESCRIPTOR_REQUEST";

		case TS_URB_RESERVE_0X002B:
			return "TS_URB_RESERVE_0X002B";

		case TS_URB_RESERVE_0X002C:
			return "TS_URB_RESERVE_0X002C";

		case TS_URB_RESERVE_0X002D:
			return "TS_URB_RESERVE_0X002D";

		case TS_URB_RESERVE_0X002E:
			return "TS_URB_RESERVE_0X002E";

		case TS_URB_RESERVE_0X002F:
			return "TS_URB_RESERVE_0X002F";

		case TS_URB_SYNC_RESET_PIPE:
			return "TS_URB_SYNC_RESET_PIPE";

		case TS_URB_SYNC_CLEAR_STALL:
			return "TS_URB_SYNC_CLEAR_STALL";

		case TS_URB_CONTROL_TRANSFER_EX:
			return "TS_URB_CONTROL_TRANSFER_EX";

		default:
			return "UNKNOWN";
	}
}

void urbdrc_dump_message(wLog* log, BOOL client, BOOL write, wStream* s)
{
	const char* type = write ? "WRITE" : "READ";
	UINT32 InterfaceId = 0;
	UINT32 MessageId = 0;
	UINT32 FunctionId = 0;
	size_t length = 0;
	size_t pos = 0;

	pos = Stream_GetPosition(s);
	if (write)
	{
		length = pos;
		Stream_ResetPosition(s);
	}
	else
		length = Stream_GetRemainingLength(s);

	if (length < 12)
		return;

	Stream_Read_UINT32(s, InterfaceId);
	Stream_Read_UINT32(s, MessageId);
	Stream_Read_UINT32(s, FunctionId);
	if (!Stream_SetPosition(s, pos))
		WLog_Print(log, WLOG_ERROR, "Stream_SetPosition(%" PRIuz ") failed", pos);

	WLog_Print(log, WLOG_DEBUG,
	           "[%-5s] %s [%08" PRIx32 "] InterfaceId=%08" PRIx32 ", MessageId=%08" PRIx32
	           ", FunctionId=%08" PRIx32 ", length=%" PRIuz,
	           type, call_to_string(client, InterfaceId, FunctionId), FunctionId, InterfaceId,
	           MessageId, FunctionId, length);
#if defined(WITH_DEBUG_URBDRC)
	if (write)
		WLog_Print(log, WLOG_TRACE, "-------------------------- URBDRC sent: ---");
	else
		WLog_Print(log, WLOG_TRACE, "-------------------------- URBDRC received:");
	winpr_HexLogDump(log, WLOG_TRACE, Stream_Buffer(s), length);
	WLog_Print(log, WLOG_TRACE, "-------------------------- URBDRC end -----");
#endif
}

/* [MS-RDPEUSB] 2.2.1 Shared Message Header (SHARED_MSG_HEADER) */
BOOL write_shared_message_header_with_functionid(wStream* s, UINT32 InterfaceId, UINT32 MessageId,
                                                 UINT32 FunctionId)
{
	if (!Stream_EnsureRemainingCapacity(s, 12))
		return FALSE;

	Stream_Write_UINT32(s, InterfaceId);
	Stream_Write_UINT32(s, MessageId);
	Stream_Write_UINT32(s, FunctionId);
	return TRUE;
}

wStream* create_shared_message_header_with_functionid(UINT32 InterfaceId, UINT32 MessageId,
                                                      UINT32 FunctionId, size_t OutputSize)
{
	wStream* out = Stream_New(nullptr, 12ULL + OutputSize);
	if (!out)
		return nullptr;
	if (!write_shared_message_header_with_functionid(out, InterfaceId, MessageId, FunctionId))
	{
		Stream_Free(out, TRUE);
		return nullptr;
	}
	return out;
}
