/**
 * FreeRDP: A Remote Desktop Protocol Implementation
 * SCard utility functions
 *
 * Copyright 2021 Armin Novak <armin.novak@thincast.com>
 * Copyright 2021 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 <winpr/wlog.h>
#include <winpr/print.h>
#include <winpr/smartcard.h>

#include <freerdp/utils/rdpdr_utils.h>
#include <freerdp/channels/scard.h>
#include <freerdp/channels/rdpdr.h>

#include <freerdp/log.h>

LONG scard_log_status_error(const char* tag, const char* what, LONG status)
{
	wLog* log = WLog_Get(tag);
	return scard_log_status_error_wlog(log, "%s", status, what);
}

LONG scard_log_status_error_wlog(wLog* log, const char* what, LONG status, ...)
{
	if (status != SCARD_S_SUCCESS)
	{
		DWORD level = WLOG_ERROR;
		switch (status)
		{
			case SCARD_W_RESET_CARD:
			case SCARD_E_NOT_TRANSACTED:
			case SCARD_E_CANCELLED:
			case SCARD_E_UNSUPPORTED_FEATURE:
			case SCARD_E_TIMEOUT:
				level = WLOG_DEBUG;
				break;
			case SCARD_E_NO_READERS_AVAILABLE:
				level = WLOG_INFO;
				break;
			default:
				break;
		}

		char* str = nullptr;
		size_t slen = 0;
		va_list ap = WINPR_C_ARRAY_INIT;
		va_start(ap, status);
		winpr_vasprintf(&str, &slen, what, ap);
		va_end(ap);
		WLog_Print(log, level, "%s failed with error %s [%" PRId32 "]", str,
		           SCardGetErrorString(status), status);
		free(str);
	}
	return status;
}

const char* scard_get_ioctl_string(UINT32 ioControlCode, BOOL funcName)
{
	switch (ioControlCode)
	{
		case SCARD_IOCTL_ESTABLISHCONTEXT:
			return funcName ? "SCardEstablishContext" : "SCARD_IOCTL_ESTABLISHCONTEXT";

		case SCARD_IOCTL_RELEASECONTEXT:
			return funcName ? "SCardReleaseContext" : "SCARD_IOCTL_RELEASECONTEXT";

		case SCARD_IOCTL_ISVALIDCONTEXT:
			return funcName ? "SCardIsValidContext" : "SCARD_IOCTL_ISVALIDCONTEXT";

		case SCARD_IOCTL_LISTREADERGROUPSA:
			return funcName ? "SCardListReaderGroupsA" : "SCARD_IOCTL_LISTREADERGROUPSA";

		case SCARD_IOCTL_LISTREADERGROUPSW:
			return funcName ? "SCardListReaderGroupsW" : "SCARD_IOCTL_LISTREADERGROUPSW";

		case SCARD_IOCTL_LISTREADERSA:
			return funcName ? "SCardListReadersA" : "SCARD_IOCTL_LISTREADERSA";

		case SCARD_IOCTL_LISTREADERSW:
			return funcName ? "SCardListReadersW" : "SCARD_IOCTL_LISTREADERSW";

		case SCARD_IOCTL_INTRODUCEREADERGROUPA:
			return funcName ? "SCardIntroduceReaderGroupA" : "SCARD_IOCTL_INTRODUCEREADERGROUPA";

		case SCARD_IOCTL_INTRODUCEREADERGROUPW:
			return funcName ? "SCardIntroduceReaderGroupW" : "SCARD_IOCTL_INTRODUCEREADERGROUPW";

		case SCARD_IOCTL_FORGETREADERGROUPA:
			return funcName ? "SCardForgetReaderGroupA" : "SCARD_IOCTL_FORGETREADERGROUPA";

		case SCARD_IOCTL_FORGETREADERGROUPW:
			return funcName ? "SCardForgetReaderGroupW" : "SCARD_IOCTL_FORGETREADERGROUPW";

		case SCARD_IOCTL_INTRODUCEREADERA:
			return funcName ? "SCardIntroduceReaderA" : "SCARD_IOCTL_INTRODUCEREADERA";

		case SCARD_IOCTL_INTRODUCEREADERW:
			return funcName ? "SCardIntroduceReaderW" : "SCARD_IOCTL_INTRODUCEREADERW";

		case SCARD_IOCTL_FORGETREADERA:
			return funcName ? "SCardForgetReaderA" : "SCARD_IOCTL_FORGETREADERA";

		case SCARD_IOCTL_FORGETREADERW:
			return funcName ? "SCardForgetReaderW" : "SCARD_IOCTL_FORGETREADERW";

		case SCARD_IOCTL_ADDREADERTOGROUPA:
			return funcName ? "SCardAddReaderToGroupA" : "SCARD_IOCTL_ADDREADERTOGROUPA";

		case SCARD_IOCTL_ADDREADERTOGROUPW:
			return funcName ? "SCardAddReaderToGroupW" : "SCARD_IOCTL_ADDREADERTOGROUPW";

		case SCARD_IOCTL_REMOVEREADERFROMGROUPA:
			return funcName ? "SCardRemoveReaderFromGroupA" : "SCARD_IOCTL_REMOVEREADERFROMGROUPA";

		case SCARD_IOCTL_REMOVEREADERFROMGROUPW:
			return funcName ? "SCardRemoveReaderFromGroupW" : "SCARD_IOCTL_REMOVEREADERFROMGROUPW";

		case SCARD_IOCTL_LOCATECARDSA:
			return funcName ? "SCardLocateCardsA" : "SCARD_IOCTL_LOCATECARDSA";

		case SCARD_IOCTL_LOCATECARDSW:
			return funcName ? "SCardLocateCardsW" : "SCARD_IOCTL_LOCATECARDSW";

		case SCARD_IOCTL_GETSTATUSCHANGEA:
			return funcName ? "SCardGetStatusChangeA" : "SCARD_IOCTL_GETSTATUSCHANGEA";

		case SCARD_IOCTL_GETSTATUSCHANGEW:
			return funcName ? "SCardGetStatusChangeW" : "SCARD_IOCTL_GETSTATUSCHANGEW";

		case SCARD_IOCTL_CANCEL:
			return funcName ? "SCardCancel" : "SCARD_IOCTL_CANCEL";

		case SCARD_IOCTL_CONNECTA:
			return funcName ? "SCardConnectA" : "SCARD_IOCTL_CONNECTA";

		case SCARD_IOCTL_CONNECTW:
			return funcName ? "SCardConnectW" : "SCARD_IOCTL_CONNECTW";

		case SCARD_IOCTL_RECONNECT:
			return funcName ? "SCardReconnect" : "SCARD_IOCTL_RECONNECT";

		case SCARD_IOCTL_DISCONNECT:
			return funcName ? "SCardDisconnect" : "SCARD_IOCTL_DISCONNECT";

		case SCARD_IOCTL_BEGINTRANSACTION:
			return funcName ? "SCardBeginTransaction" : "SCARD_IOCTL_BEGINTRANSACTION";

		case SCARD_IOCTL_ENDTRANSACTION:
			return funcName ? "SCardEndTransaction" : "SCARD_IOCTL_ENDTRANSACTION";

		case SCARD_IOCTL_STATE:
			return funcName ? "SCardState" : "SCARD_IOCTL_STATE";

		case SCARD_IOCTL_STATUSA:
			return funcName ? "SCardStatusA" : "SCARD_IOCTL_STATUSA";

		case SCARD_IOCTL_STATUSW:
			return funcName ? "SCardStatusW" : "SCARD_IOCTL_STATUSW";

		case SCARD_IOCTL_TRANSMIT:
			return funcName ? "SCardTransmit" : "SCARD_IOCTL_TRANSMIT";

		case SCARD_IOCTL_CONTROL:
			return funcName ? "SCardControl" : "SCARD_IOCTL_CONTROL";

		case SCARD_IOCTL_GETATTRIB:
			return funcName ? "SCardGetAttrib" : "SCARD_IOCTL_GETATTRIB";

		case SCARD_IOCTL_SETATTRIB:
			return funcName ? "SCardSetAttrib" : "SCARD_IOCTL_SETATTRIB";

		case SCARD_IOCTL_ACCESSSTARTEDEVENT:
			return funcName ? "SCardAccessStartedEvent" : "SCARD_IOCTL_ACCESSSTARTEDEVENT";

		case SCARD_IOCTL_LOCATECARDSBYATRA:
			return funcName ? "SCardLocateCardsByATRA" : "SCARD_IOCTL_LOCATECARDSBYATRA";

		case SCARD_IOCTL_LOCATECARDSBYATRW:
			return funcName ? "SCardLocateCardsByATRB" : "SCARD_IOCTL_LOCATECARDSBYATRW";

		case SCARD_IOCTL_READCACHEA:
			return funcName ? "SCardReadCacheA" : "SCARD_IOCTL_READCACHEA";

		case SCARD_IOCTL_READCACHEW:
			return funcName ? "SCardReadCacheW" : "SCARD_IOCTL_READCACHEW";

		case SCARD_IOCTL_WRITECACHEA:
			return funcName ? "SCardWriteCacheA" : "SCARD_IOCTL_WRITECACHEA";

		case SCARD_IOCTL_WRITECACHEW:
			return funcName ? "SCardWriteCacheW" : "SCARD_IOCTL_WRITECACHEW";

		case SCARD_IOCTL_GETTRANSMITCOUNT:
			return funcName ? "SCardGetTransmitCount" : "SCARD_IOCTL_GETTRANSMITCOUNT";

		case SCARD_IOCTL_RELEASETARTEDEVENT:
			return funcName ? "SCardReleaseStartedEvent" : "SCARD_IOCTL_RELEASETARTEDEVENT";

		case SCARD_IOCTL_GETREADERICON:
			return funcName ? "SCardGetReaderIcon" : "SCARD_IOCTL_GETREADERICON";

		case SCARD_IOCTL_GETDEVICETYPEID:
			return funcName ? "SCardGetDeviceTypeId" : "SCARD_IOCTL_GETDEVICETYPEID";

		default:
			return funcName ? "SCardUnknown" : "SCARD_IOCTL_UNKNOWN";
	}
}

const char* rdpdr_component_string(UINT16 component)
{
	switch (component)
	{
		case RDPDR_CTYP_PRN:
			return "RDPDR_CTYP_PRN";
		case RDPDR_CTYP_CORE:
			return "RDPDR_CTYP_CORE";
		default:
			return "UNKNOWN";
	}
}

const char* rdpdr_packetid_string(UINT16 packetid)
{
	switch (packetid)
	{
		case PAKID_CORE_SERVER_ANNOUNCE:
			return "PAKID_CORE_SERVER_ANNOUNCE";
		case PAKID_CORE_CLIENTID_CONFIRM:
			return "PAKID_CORE_CLIENTID_CONFIRM";
		case PAKID_CORE_CLIENT_NAME:
			return "PAKID_CORE_CLIENT_NAME";
		case PAKID_CORE_DEVICELIST_ANNOUNCE:
			return "PAKID_CORE_DEVICELIST_ANNOUNCE";
		case PAKID_CORE_DEVICE_REPLY:
			return "PAKID_CORE_DEVICE_REPLY";
		case PAKID_CORE_DEVICE_IOREQUEST:
			return "PAKID_CORE_DEVICE_IOREQUEST";
		case PAKID_CORE_DEVICE_IOCOMPLETION:
			return "PAKID_CORE_DEVICE_IOCOMPLETION";
		case PAKID_CORE_SERVER_CAPABILITY:
			return "PAKID_CORE_SERVER_CAPABILITY";
		case PAKID_CORE_CLIENT_CAPABILITY:
			return "PAKID_CORE_CLIENT_CAPABILITY";
		case PAKID_CORE_DEVICELIST_REMOVE:
			return "PAKID_CORE_DEVICELIST_REMOVE";
		case PAKID_CORE_USER_LOGGEDON:
			return "PAKID_CORE_USER_LOGGEDON";
		case PAKID_PRN_CACHE_DATA:
			return "PAKID_PRN_CACHE_DATA";
		case PAKID_PRN_USING_XPS:
			return "PAKID_PRN_USING_XPS";
		default:
			return "UNKNOWN";
	}
}

BOOL rdpdr_write_iocompletion_header(wStream* out, UINT32 DeviceId, UINT32 CompletionId,
                                     NTSTATUS ioStatus)
{
	WINPR_ASSERT(out);
	Stream_ResetPosition(out);
	if (!Stream_EnsureRemainingCapacity(out, 16))
		return FALSE;

	Stream_Write_UINT16(out, RDPDR_CTYP_CORE);                /* Component (2 bytes) */
	Stream_Write_UINT16(out, PAKID_CORE_DEVICE_IOCOMPLETION); /* PacketId (2 bytes) */
	Stream_Write_UINT32(out, DeviceId);                       /* DeviceId (4 bytes) */
	Stream_Write_UINT32(out, CompletionId);                   /* CompletionId (4 bytes) */
	Stream_Write_INT32(out, ioStatus);                        /* IoStatus (4 bytes) */

	return TRUE;
}

static void rdpdr_dump_packet(wLog* log, DWORD lvl, wStream* s, const char* custom, BOOL send)
{
	if (!WLog_IsLevelActive(log, lvl))
		return;

	const size_t gpos = Stream_GetPosition(s);
	const size_t pos = send ? Stream_GetPosition(s) : Stream_Length(s);

	UINT16 component = 0;
	UINT16 packetid = 0;

	Stream_ResetPosition(s);

	if (pos >= 2)
		Stream_Read_UINT16(s, component);
	if (pos >= 4)
		Stream_Read_UINT16(s, packetid);

	switch (packetid)
	{
		case PAKID_CORE_SERVER_ANNOUNCE:
		case PAKID_CORE_CLIENTID_CONFIRM:
		{
			UINT16 versionMajor = 0;
			UINT16 versionMinor = 0;
			UINT32 clientID = 0;

			if (pos >= 6)
				Stream_Read_UINT16(s, versionMajor);
			if (pos >= 8)
				Stream_Read_UINT16(s, versionMinor);
			if (pos >= 12)
				Stream_Read_UINT32(s, clientID);
			WLog_Print(log, lvl,
			           "%s [%s | %s] [version:%" PRIu16 ".%" PRIu16 "][id:0x%08" PRIx32
			           "] -> %" PRIuz,
			           custom, rdpdr_component_string(component), rdpdr_packetid_string(packetid),
			           versionMajor, versionMinor, clientID, pos);
		}
		break;
		case PAKID_CORE_CLIENT_NAME:
		{
			char name[256] = WINPR_C_ARRAY_INIT;
			UINT32 unicodeFlag = 0;
			UINT32 codePage = 0;
			UINT32 computerNameLen = 0;
			if (pos >= 8)
				Stream_Read_UINT32(s, unicodeFlag);
			if (pos >= 12)
				Stream_Read_UINT32(s, codePage);
			if (pos >= 16)
				Stream_Read_UINT32(s, computerNameLen);
			if (pos >= 16 + computerNameLen)
			{
				if (unicodeFlag == 0)
					Stream_Read(s, name, MIN(sizeof(name), computerNameLen));
				else
					(void)ConvertWCharNToUtf8(Stream_ConstPointer(s),
					                          computerNameLen / sizeof(WCHAR), name, sizeof(name));
			}
			WLog_Print(log, lvl,
			           "%s [%s | %s] [ucs:%" PRIu32 "|cp:%" PRIu32 "][len:0x%08" PRIx32
			           "] '%s' -> %" PRIuz,
			           custom, rdpdr_component_string(component), rdpdr_packetid_string(packetid),
			           unicodeFlag, codePage, computerNameLen, name, pos);
		}
		break;

		case PAKID_CORE_DEVICE_IOREQUEST:
		{
			UINT32 CompletionId = 0;
			UINT32 deviceID = 0;
			UINT32 FileId = 0;
			UINT32 MajorFunction = 0;
			UINT32 MinorFunction = 0;

			if (pos >= 8)
				Stream_Read_UINT32(s, deviceID);
			if (pos >= 12)
				Stream_Read_UINT32(s, FileId);
			if (pos >= 16)
				Stream_Read_UINT32(s, CompletionId);
			if (pos >= 20)
				Stream_Read_UINT32(s, MajorFunction);
			if (pos >= 24)
				Stream_Read_UINT32(s, MinorFunction);
			WLog_Print(log, lvl,
			           "%s [%s | %s] [0x%08" PRIx32 "] FileId=0x%08" PRIx32
			           ", CompletionId=0x%08" PRIx32 ", MajorFunction=0x%08" PRIx32
			           ", MinorFunction=0x%08" PRIx32 " -> %" PRIuz,
			           custom, rdpdr_component_string(component), rdpdr_packetid_string(packetid),
			           deviceID, FileId, CompletionId, MajorFunction, MinorFunction, pos);
		}
		break;
		case PAKID_CORE_DEVICE_IOCOMPLETION:
		{
			UINT32 completionID = 0;
			UINT32 ioStatus = 0;
			UINT32 deviceID = 0;
			if (pos >= 8)
				Stream_Read_UINT32(s, deviceID);
			if (pos >= 12)
				Stream_Read_UINT32(s, completionID);
			if (pos >= 16)
				Stream_Read_UINT32(s, ioStatus);

			WLog_Print(log, lvl,
			           "%s [%s | %s] [0x%08" PRIx32 "] completionID=0x%08" PRIx32
			           ", ioStatus=0x%08" PRIx32 " -> %" PRIuz,
			           custom, rdpdr_component_string(component), rdpdr_packetid_string(packetid),
			           deviceID, completionID, ioStatus, pos);
		}
		break;
		case PAKID_CORE_DEVICE_REPLY:
		{
			UINT32 deviceID = 0;
			UINT32 status = 0;

			if (pos >= 8)
				Stream_Read_UINT32(s, deviceID);
			if (pos >= 12)
				Stream_Read_UINT32(s, status);
			WLog_Print(log, lvl,
			           "%s [%s | %s] [id:0x%08" PRIx32 ",status=0x%08" PRIx32 "] -> %" PRIuz,
			           custom, rdpdr_component_string(component), rdpdr_packetid_string(packetid),
			           deviceID, status, pos);
		}
		break;
		case PAKID_CORE_CLIENT_CAPABILITY:
		case PAKID_CORE_SERVER_CAPABILITY:
		{
			UINT16 numCapabilities = 0;
			if (pos >= 6)
				Stream_Read_UINT16(s, numCapabilities);
			if (pos >= 8)
				Stream_Seek_UINT16(s); /* padding */
			WLog_Print(log, lvl, "%s [%s | %s] [caps:%" PRIu16 "] -> %" PRIuz, custom,
			           rdpdr_component_string(component), rdpdr_packetid_string(packetid),
			           numCapabilities, pos);
			for (UINT16 x = 0; x < numCapabilities; x++)
			{
				RDPDR_CAPABILITY_HEADER header = WINPR_C_ARRAY_INIT;
				const UINT error = rdpdr_read_capset_header(log, s, &header);
				if (error == CHANNEL_RC_OK)
					Stream_Seek(s, header.CapabilityLength);
			}
		}
		break;
		case PAKID_CORE_DEVICELIST_ANNOUNCE:
		{
			size_t offset = 8;
			UINT32 count = 0;

			if (pos >= offset)
				Stream_Read_UINT32(s, count);

			WLog_Print(log, lvl, "%s [%s | %s] [%" PRIu32 "] -> %" PRIuz, custom,
			           rdpdr_component_string(component), rdpdr_packetid_string(packetid), count,
			           pos);

			for (UINT32 x = 0; x < count; x++)
			{
				RdpdrDevice device = WINPR_C_ARRAY_INIT;

				offset += 20;
				if (pos >= offset)
				{
					Stream_Read_UINT32(s, device.DeviceType);       /* DeviceType (4 bytes) */
					Stream_Read_UINT32(s, device.DeviceId);         /* DeviceId (4 bytes) */
					Stream_Read(
					    s, device.PreferredDosName,
					    ARRAYSIZE(device.PreferredDosName));        /* PreferredDosName (8 bytes) */
					Stream_Read_UINT32(s, device.DeviceDataLength); /* DeviceDataLength (4 bytes) */
					device.DeviceData = Stream_Pointer(s);
					Stream_Seek(s, device.DeviceDataLength);
				}
				offset += device.DeviceDataLength;

				WLog_Print(log, lvl,
				           "%s [announce][%" PRIu32 "] %s [0x%08" PRIx32
				           "] '%s' [DeviceDataLength=%" PRIu32 "]",
				           custom, x, freerdp_rdpdr_dtyp_string(device.DeviceType), device.DeviceId,
				           device.PreferredDosName, device.DeviceDataLength);
			}
		}
		break;
		case PAKID_CORE_DEVICELIST_REMOVE:
		{
			size_t offset = 8;
			UINT32 count = 0;

			if (pos >= offset)
				Stream_Read_UINT32(s, count);

			WLog_Print(log, lvl, "%s [%s | %s] [%" PRIu32 "] -> %" PRIuz, custom,
			           rdpdr_component_string(component), rdpdr_packetid_string(packetid), count,
			           pos);

			for (UINT32 x = 0; x < count; x++)
			{
				UINT32 id = 0;

				offset += 4;
				if (pos >= offset)
					Stream_Read_UINT32(s, id);

				WLog_Print(log, lvl, "%s [remove][%" PRIu32 "] id=%" PRIu32, custom, x, id);
			}
		}
		break;
		case PAKID_CORE_USER_LOGGEDON:
			WLog_Print(log, lvl, "%s [%s | %s] -> %" PRIuz, custom,
			           rdpdr_component_string(component), rdpdr_packetid_string(packetid), pos);
			break;
		default:
		{
			WLog_Print(log, lvl, "%s [%s | %s] -> %" PRIuz, custom,
			           rdpdr_component_string(component), rdpdr_packetid_string(packetid), pos);
		}
		break;
	}

	// winpr_HexLogDump(log, lvl, Stream_Buffer(s), pos);
	if (!Stream_SetPosition(s, gpos))
		WLog_Print(log, WLOG_ERROR, "Stream_SetPosition(%" PRIuz ") failed", gpos);
}

void rdpdr_dump_received_packet(wLog* log, DWORD lvl, wStream* out, const char* custom)
{
	rdpdr_dump_packet(log, lvl, out, custom, FALSE);
}

void rdpdr_dump_send_packet(wLog* log, DWORD lvl, wStream* out, const char* custom)
{
	rdpdr_dump_packet(log, lvl, out, custom, TRUE);
}

const char* rdpdr_irp_string(UINT32 major)
{
	switch (major)
	{
		case IRP_MJ_CREATE:
			return "IRP_MJ_CREATE";
		case IRP_MJ_CLOSE:
			return "IRP_MJ_CLOSE";
		case IRP_MJ_READ:
			return "IRP_MJ_READ";
		case IRP_MJ_WRITE:
			return "IRP_MJ_WRITE";
		case IRP_MJ_DEVICE_CONTROL:
			return "IRP_MJ_DEVICE_CONTROL";
		case IRP_MJ_QUERY_VOLUME_INFORMATION:
			return "IRP_MJ_QUERY_VOLUME_INFORMATION";
		case IRP_MJ_SET_VOLUME_INFORMATION:
			return "IRP_MJ_SET_VOLUME_INFORMATION";
		case IRP_MJ_QUERY_INFORMATION:
			return "IRP_MJ_QUERY_INFORMATION";
		case IRP_MJ_SET_INFORMATION:
			return "IRP_MJ_SET_INFORMATION";
		case IRP_MJ_DIRECTORY_CONTROL:
			return "IRP_MJ_DIRECTORY_CONTROL";
		case IRP_MJ_LOCK_CONTROL:
			return "IRP_MJ_LOCK_CONTROL";
		default:
			return "IRP_UNKNOWN";
	}
}

const char* rdpdr_cap_type_string(UINT16 capability)
{
	switch (capability)
	{
		case CAP_GENERAL_TYPE:
			return "CAP_GENERAL_TYPE";
		case CAP_PRINTER_TYPE:
			return "CAP_PRINTER_TYPE";
		case CAP_PORT_TYPE:
			return "CAP_PORT_TYPE";
		case CAP_DRIVE_TYPE:
			return "CAP_DRIVE_TYPE";
		case CAP_SMARTCARD_TYPE:
			return "CAP_SMARTCARD_TYPE";
		default:
			return "CAP_UNKNOWN";
	}
}

UINT rdpdr_read_capset_header(wLog* log, wStream* s, RDPDR_CAPABILITY_HEADER* header)
{
	WINPR_ASSERT(header);
	if (!Stream_CheckAndLogRequiredLengthWLog(log, s, 8))
		return ERROR_INVALID_DATA;

	Stream_Read_UINT16(s, header->CapabilityType);   /* CapabilityType (2 bytes) */
	Stream_Read_UINT16(s, header->CapabilityLength); /* CapabilityLength (2 bytes) */
	Stream_Read_UINT32(s, header->Version);          /* Version (4 bytes) */

	WLog_Print(log, WLOG_TRACE,
	           "capability %s [0x%04" PRIx16 "] got version %" PRIu32 ", length %" PRIu16,
	           rdpdr_cap_type_string(header->CapabilityType), header->CapabilityType,
	           header->Version, header->CapabilityLength);
	if (header->CapabilityLength < 8)
	{
		WLog_Print(log, WLOG_ERROR, "capability %s got short length %" PRIu32,
		           rdpdr_cap_type_string(header->CapabilityType), header->CapabilityLength);
		return ERROR_INVALID_DATA;
	}
	header->CapabilityLength -= 8;
	if (!Stream_CheckAndLogRequiredLengthWLog(log, s, header->CapabilityLength))
		return ERROR_INVALID_DATA;
	return CHANNEL_RC_OK;
}

UINT rdpdr_write_capset_header(wLog* log, wStream* s, const RDPDR_CAPABILITY_HEADER* header)
{
	WINPR_ASSERT(header);
	WINPR_ASSERT(header->CapabilityLength >= 8);

	if (!Stream_EnsureRemainingCapacity(s, header->CapabilityLength))
	{
		WLog_Print(log, WLOG_ERROR, "not enough data in stream!");
		return ERROR_INVALID_DATA;
	}

	WLog_Print(log, WLOG_TRACE, "writing capability %s version %" PRIu32 ", length %" PRIu16,
	           rdpdr_cap_type_string(header->CapabilityType), header->Version,
	           header->CapabilityLength);
	Stream_Write_UINT16(s, header->CapabilityType);   /* CapabilityType (2 bytes) */
	Stream_Write_UINT16(s, header->CapabilityLength); /* CapabilityLength (2 bytes) */
	Stream_Write_UINT32(s, header->Version);          /* Version (4 bytes) */
	return CHANNEL_RC_OK;
}

const char* rdpdr_irp_val2str(UINT32 ioCode1)
{
	switch (ioCode1)
	{
		case RDPDR_IRP_MJ_CREATE:
			return "MJ_CREATE";
		case RDPDR_IRP_MJ_CLEANUP:
			return "MJ_CLEANUP";
		case RDPDR_IRP_MJ_CLOSE:
			return "MJ_CLOSE";
		case RDPDR_IRP_MJ_READ:
			return "MJ_READ";
		case RDPDR_IRP_MJ_WRITE:
			return "MJ_WRITE";
		case RDPDR_IRP_MJ_FLUSH_BUFFERS:
			return "MJ_FLUSH_BUFFERS";
		case RDPDR_IRP_MJ_SHUTDOWN:
			return "MJ_SHUTDOWN";
		case RDPDR_IRP_MJ_DEVICE_CONTROL:
			return "MJ_DEVICE_CONTROL";
		case RDPDR_IRP_MJ_QUERY_VOLUME_INFORMATION:
			return "MJ_QUERY_VOLUME_INFORMATION";
		case RDPDR_IRP_MJ_SET_VOLUME_INFORMATION:
			return "MJ_SET_VOLUME_INFORMATION";
		case RDPDR_IRP_MJ_QUERY_INFORMATION:
			return "MJ_QUERY_INFORMATION";
		case RDPDR_IRP_MJ_SET_INFORMATION:
			return "MJ_SET_INFORMATION";
		case RDPDR_IRP_MJ_DIRECTORY_CONTROL:
			return "MJ_DIRECTORY_CONTROL";
		case RDPDR_IRP_MJ_LOCK_CONTROL:
			return "MJ_LOCK_CONTROL";
		case RDPDR_IRP_MJ_QUERY_SECURITY:
			return "MJ_QUERY_SECURITY";
		case RDPDR_IRP_MJ_SET_SECURITY:
			return "MJ_SET_SECURITY";
		default:
			return "IRP_MJ_UNKNOWN";
	}
}

const char* rdpdr_irp_mask2str(UINT32 ioCode1Mask, char* buffer, size_t len)
{
	if (len < 1)
		return nullptr;

	if (!winpr_str_append("{", buffer, len, nullptr))
		return nullptr;

	for (size_t x = 0; x < 32; x++)
	{
		const UINT32 mask = (1u << x);
		if (ioCode1Mask & mask)
		{
			if (!winpr_str_append(rdpdr_irp_val2str(mask), &buffer[1], len - 1, "|"))
				return nullptr;
		}
	}

	char number[16] = WINPR_C_ARRAY_INIT;
	(void)_snprintf(number, sizeof(number), "}[0x%08" PRIx32 "]", ioCode1Mask);
	if (!winpr_str_append(number, buffer, len, nullptr))
		return nullptr;
	return buffer;
}

const char* rdpdr_device_type_string(UINT32 type)
{
	switch (type)
	{
		case RDPDR_DTYP_SERIAL:
			return "serial";
		case RDPDR_DTYP_PRINT:
			return "printer";
		case RDPDR_DTYP_FILESYSTEM:
			return "drive";
		case RDPDR_DTYP_SMARTCARD:
			return "smartcard";
		case RDPDR_DTYP_PARALLEL:
			return "parallel";
		default:
			return "UNKNOWN";
	}
}
