/**
 * WinPR: Windows Portable Runtime
 * Smart Card API
 *
 * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
 * Copyright 2020 Armin Novak <armin.novak@thincast.com>
 * Copyright 2020 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/config.h>

#ifndef _WIN32

#ifdef __APPLE__
#include <sys/types.h>
#include <sys/param.h>
#include <sys/sysctl.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#endif

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

#include <winpr/crt.h>
#include <winpr/assert.h>
#include <winpr/synch.h>
#include <winpr/library.h>
#include <winpr/smartcard.h>
#include <winpr/collections.h>
#include <winpr/environment.h>

#include "smartcard_pcsc.h"

#include "../log.h"
#define TAG WINPR_TAG("smartcard")

#ifndef MIN
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#endif

#define WINSCARD_LOAD_PROC_EX(module, pcsc, _fname, _name)                   \
	do                                                                       \
	{                                                                        \
		WINPR_PRAGMA_DIAG_PUSH                                               \
		WINPR_PRAGMA_DIAG_IGNORED_PEDANTIC                                   \
		pcsc.pfn##_fname = GetProcAddressAs(module, #_name, fnPCSC##_fname); \
		WINPR_PRAGMA_DIAG_POP                                                \
	} while (0)

#define WINSCARD_LOAD_PROC(module, pcsc, _name) WINSCARD_LOAD_PROC_EX(module, pcsc, _name, _name)

/**
 * PC/SC transactions:
 * http://developersblog.wwpass.com/?p=180
 */

/**
 * Smart Card Logon on Windows Vista:
 * http://blogs.msdn.com/b/shivaram/archive/2007/02/26/smart-card-logon-on-windows-vista.aspx
 */

/**
 * The Smart Card Cryptographic Service Provider Cookbook:
 * http://msdn.microsoft.com/en-us/library/ms953432.aspx
 *
 * SCARDCONTEXT
 *
 * The context is a communication channel with the smart card resource manager and
 * all calls to the resource manager must go through this link.
 *
 * All functions that take a context as a parameter or a card handle as parameter,
 * which is indirectly associated with a particular context, may be blocking calls.
 * Examples of these are SCardGetStatusChange and SCardBeginTransaction, which takes
 * a card handle as a parameter. If such a function blocks then all operations wanting
 * to use the context are blocked as well. So, it is recommended that a CSP using
 * monitoring establishes at least two contexts with the resource manager; one for
 * monitoring (with SCardGetStatusChange) and one for other operations.
 *
 * If multiple cards are present, it is recommended that a separate context or pair
 * of contexts be established for each card to prevent operations on one card from
 * blocking operations on another.
 *
 * Example one
 *
 * The example below shows what can happen if a CSP using SCardGetStatusChange for
 * monitoring does not establish two contexts with the resource manager.
 * The context becomes unusable until SCardGetStatusChange unblocks.
 *
 * In this example, there is one process running called P1.
 * P1 calls SCardEstablishContext, which returns the context hCtx.
 * P1 calls SCardConnect (with the hCtx context) which returns a handle to the card, hCard.
 * P1 calls SCardGetStatusChange (with the hCtx context) which blocks because
 * there are no status changes to report.
 * Until the thread running SCardGetStatusChange unblocks, another thread in P1 trying to
 * perform an operation using the context hCtx (or the card hCard) will also be blocked.
 *
 * Example two
 *
 * The example below shows how transaction control ensures that operations meant to be
 * performed without interruption can do so safely within a transaction.
 *
 * In this example, there are two different processes running; P1 and P2.
 * P1 calls SCardEstablishContext, which returns the context hCtx1.
 * P2 calls SCardEstablishContext, which returns the context hCtx2.
 * P1 calls SCardConnect (with the hCtx1 context) which returns a handle to the card, hCard1.
 * P2 calls SCardConnect (with the hCtx2 context) which returns a handle to the same card, hCard2.
 * P1 calls SCardBeginTransaction (with the hCard 1 context).
 * Until P1 calls SCardEndTransaction (with the hCard1 context),
 * any operation using hCard2 will be blocked.
 * Once an operation using hCard2 is blocked and until it's returning,
 * any operation using hCtx2 (and hCard2) will also be blocked.
 */

//#define DISABLE_PCSC_SCARD_AUTOALLOCATE
#include "smartcard_pcsc.h"

#define PCSC_SCARD_PCI_T0 (&g_PCSC_rgSCardT0Pci)
#define PCSC_SCARD_PCI_T1 (&g_PCSC_rgSCardT1Pci)
#define PCSC_SCARD_PCI_RAW (&g_PCSC_rgSCardRawPci)

typedef PCSC_LONG (*fnPCSCSCardEstablishContext)(PCSC_DWORD dwScope, LPCVOID pvReserved1,
                                                 LPCVOID pvReserved2, LPSCARDCONTEXT phContext);
typedef PCSC_LONG (*fnPCSCSCardReleaseContext)(SCARDCONTEXT hContext);
typedef PCSC_LONG (*fnPCSCSCardIsValidContext)(SCARDCONTEXT hContext);
typedef PCSC_LONG (*fnPCSCSCardConnect)(SCARDCONTEXT hContext, LPCSTR szReader,
                                        PCSC_DWORD dwShareMode, PCSC_DWORD dwPreferredProtocols,
                                        LPSCARDHANDLE phCard, PCSC_LPDWORD pdwActiveProtocol);
typedef PCSC_LONG (*fnPCSCSCardReconnect)(SCARDHANDLE hCard, PCSC_DWORD dwShareMode,
                                          PCSC_DWORD dwPreferredProtocols,
                                          PCSC_DWORD dwInitialization,
                                          PCSC_LPDWORD pdwActiveProtocol);
typedef PCSC_LONG (*fnPCSCSCardDisconnect)(SCARDHANDLE hCard, PCSC_DWORD dwDisposition);
typedef PCSC_LONG (*fnPCSCSCardBeginTransaction)(SCARDHANDLE hCard);
typedef PCSC_LONG (*fnPCSCSCardEndTransaction)(SCARDHANDLE hCard, PCSC_DWORD dwDisposition);
typedef PCSC_LONG (*fnPCSCSCardStatus)(SCARDHANDLE hCard, LPSTR mszReaderName,
                                       PCSC_LPDWORD pcchReaderLen, PCSC_LPDWORD pdwState,
                                       PCSC_LPDWORD pdwProtocol, LPBYTE pbAtr,
                                       PCSC_LPDWORD pcbAtrLen);
typedef PCSC_LONG (*fnPCSCSCardGetStatusChange)(SCARDCONTEXT hContext, PCSC_DWORD dwTimeout,
                                                PCSC_SCARD_READERSTATE* rgReaderStates,
                                                PCSC_DWORD cReaders);
typedef PCSC_LONG (*fnPCSCSCardControl)(SCARDHANDLE hCard, PCSC_DWORD dwControlCode,
                                        LPCVOID pbSendBuffer, PCSC_DWORD cbSendLength,
                                        LPVOID pbRecvBuffer, PCSC_DWORD cbRecvLength,
                                        PCSC_LPDWORD lpBytesReturned);
typedef PCSC_LONG (*fnPCSCSCardTransmit)(SCARDHANDLE hCard, const PCSC_SCARD_IO_REQUEST* pioSendPci,
                                         LPCBYTE pbSendBuffer, PCSC_DWORD cbSendLength,
                                         PCSC_SCARD_IO_REQUEST* pioRecvPci, LPBYTE pbRecvBuffer,
                                         PCSC_LPDWORD pcbRecvLength);
typedef PCSC_LONG (*fnPCSCSCardListReaderGroups)(SCARDCONTEXT hContext, LPSTR mszGroups,
                                                 PCSC_LPDWORD pcchGroups);
typedef PCSC_LONG (*fnPCSCSCardListReaders)(SCARDCONTEXT hContext, LPCSTR mszGroups,
                                            LPSTR mszReaders, PCSC_LPDWORD pcchReaders);
typedef PCSC_LONG (*fnPCSCSCardFreeMemory)(SCARDCONTEXT hContext, LPCVOID pvMem);
typedef PCSC_LONG (*fnPCSCSCardCancel)(SCARDCONTEXT hContext);
typedef PCSC_LONG (*fnPCSCSCardGetAttrib)(SCARDHANDLE hCard, PCSC_DWORD dwAttrId, LPBYTE pbAttr,
                                          PCSC_LPDWORD pcbAttrLen);
typedef PCSC_LONG (*fnPCSCSCardSetAttrib)(SCARDHANDLE hCard, PCSC_DWORD dwAttrId, LPCBYTE pbAttr,
                                          PCSC_DWORD cbAttrLen);

typedef struct
{
	WINPR_ATTR_NODISCARD fnPCSCSCardEstablishContext pfnSCardEstablishContext;
	WINPR_ATTR_NODISCARD fnPCSCSCardReleaseContext pfnSCardReleaseContext;
	WINPR_ATTR_NODISCARD fnPCSCSCardIsValidContext pfnSCardIsValidContext;
	WINPR_ATTR_NODISCARD fnPCSCSCardConnect pfnSCardConnect;
	WINPR_ATTR_NODISCARD fnPCSCSCardReconnect pfnSCardReconnect;
	WINPR_ATTR_NODISCARD fnPCSCSCardDisconnect pfnSCardDisconnect;
	WINPR_ATTR_NODISCARD fnPCSCSCardBeginTransaction pfnSCardBeginTransaction;
	WINPR_ATTR_NODISCARD fnPCSCSCardEndTransaction pfnSCardEndTransaction;
	WINPR_ATTR_NODISCARD fnPCSCSCardStatus pfnSCardStatus;
	WINPR_ATTR_NODISCARD fnPCSCSCardGetStatusChange pfnSCardGetStatusChange;
	WINPR_ATTR_NODISCARD fnPCSCSCardControl pfnSCardControl;
	WINPR_ATTR_NODISCARD fnPCSCSCardTransmit pfnSCardTransmit;
	WINPR_ATTR_NODISCARD fnPCSCSCardListReaderGroups pfnSCardListReaderGroups;
	WINPR_ATTR_NODISCARD fnPCSCSCardListReaders pfnSCardListReaders;
	fnPCSCSCardFreeMemory pfnSCardFreeMemory;
	fnPCSCSCardCancel pfnSCardCancel;
	WINPR_ATTR_NODISCARD fnPCSCSCardGetAttrib pfnSCardGetAttrib;
	WINPR_ATTR_NODISCARD fnPCSCSCardSetAttrib pfnSCardSetAttrib;
} PCSCFunctionTable;

typedef struct
{
	DWORD len;
	DWORD freshness;
	BYTE* data;
} PCSC_CACHE_ITEM;

typedef struct
{
	SCARDHANDLE owner;
	CRITICAL_SECTION lock;
	SCARDCONTEXT hContext;
	DWORD dwCardHandleCount;
	BOOL isTransactionLocked;
	wHashTable* cache;
} PCSC_SCARDCONTEXT;

typedef struct
{
	BOOL shared;
	SCARDCONTEXT hSharedContext;
} PCSC_SCARDHANDLE;

static HMODULE g_PCSCModule = nullptr;
static PCSCFunctionTable g_PCSC = WINPR_C_ARRAY_INIT;

static HANDLE g_StartedEvent = nullptr;
static int g_StartedEventRefCount = 0;

static BOOL g_SCardAutoAllocate = FALSE;
static BOOL g_PnP_Notification = TRUE;

#ifdef __MACOSX__
static unsigned int OSXVersion = 0;
#endif

static wListDictionary* g_CardHandles = nullptr;
static wListDictionary* g_CardContexts = nullptr;
static wListDictionary* g_MemoryBlocks = nullptr;
static INIT_ONCE g_CardHandleInitializer = INIT_ONCE_STATIC_INIT;

static const char SMARTCARD_PNP_NOTIFICATION_A[] = "\\\\?PnP?\\Notification";

static const PCSC_SCARD_IO_REQUEST g_PCSC_rgSCardT0Pci = { SCARD_PROTOCOL_T0,
	                                                       sizeof(PCSC_SCARD_IO_REQUEST) };
static const PCSC_SCARD_IO_REQUEST g_PCSC_rgSCardT1Pci = { SCARD_PROTOCOL_T1,
	                                                       sizeof(PCSC_SCARD_IO_REQUEST) };
static const PCSC_SCARD_IO_REQUEST g_PCSC_rgSCardRawPci = { PCSC_SCARD_PROTOCOL_RAW,
	                                                        sizeof(PCSC_SCARD_IO_REQUEST) };

static void clearHandles(void)
{
	ListDictionary_Free(g_CardHandles);
	ListDictionary_Free(g_CardContexts);
	ListDictionary_Free(g_MemoryBlocks);

	g_CardHandles = nullptr;
	g_CardContexts = nullptr;
	g_MemoryBlocks = nullptr;
}

WINPR_ATTR_NODISCARD
static wListDictionary* setupWithValueObjectFree(BOOL key)
{
	wListDictionary* list = ListDictionary_New(TRUE);
	if (!list)
		return nullptr;

	{
		wObject* obj = ListDictionary_ValueObject(list);
		if (key)
			obj = ListDictionary_KeyObject(list);
		WINPR_ASSERT(obj);
		obj->fnObjectFree = free;
	}
	return list;
}

static void cardContextFree(PCSC_SCARDCONTEXT* pContext)
{
	if (!pContext)
		return;

	DeleteCriticalSection(&(pContext->lock));
	HashTable_Free(pContext->cache);
	free(pContext);
}

static void cardContextFreeVoid(void* obj)
{
	PCSC_SCARDCONTEXT* pContext = obj;
	cardContextFree(pContext);
}

WINPR_ATTR_NODISCARD
static BOOL initializeHandles(WINPR_ATTR_UNUSED PINIT_ONCE InitOnce,
                              WINPR_ATTR_UNUSED PVOID Parameter, WINPR_ATTR_UNUSED PVOID* Context)
{
	(void)atexit(clearHandles);
	g_CardHandles = setupWithValueObjectFree(FALSE);
	if (!g_CardHandles)
		return FALSE;
	g_CardContexts = ListDictionary_New(TRUE);
	if (!g_CardContexts)
		return FALSE;
	{
		wObject* obj = ListDictionary_ValueObject(g_CardContexts);
		if (!obj)
			return FALSE;
		obj->fnObjectFree = cardContextFreeVoid;
	}

	g_MemoryBlocks = setupWithValueObjectFree(TRUE);
	return g_MemoryBlocks != nullptr;
}

WINPR_ATTR_NODISCARD
static BOOL init(void)
{
	return InitOnceExecuteOnce(&g_CardHandleInitializer, initializeHandles, nullptr, nullptr);
}

static LONG WINAPI PCSC_SCardFreeMemory_Internal(SCARDCONTEXT hContext, LPVOID pvMem);

WINPR_ATTR_NODISCARD
static LONG WINAPI PCSC_SCardEstablishContext_Internal(DWORD dwScope, LPCVOID pvReserved1,
                                                       LPCVOID pvReserved2,
                                                       LPSCARDCONTEXT phContext);

WINPR_ATTR_NODISCARD
static LONG WINAPI PCSC_SCardReleaseContext_Internal(SCARDCONTEXT hContext);

WINPR_ATTR_NODISCARD
static LONG PCSC_SCard_LogError(const char* what)
{
	WLog_WARN(TAG, "Missing function pointer %s=nullptr", what);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD
static LONG PCSC_MapErrorCodeToWinSCard(PCSC_LONG errorCode)
{
	/**
	 * pcsc-lite returns SCARD_E_UNEXPECTED when it
	 * should return SCARD_E_UNSUPPORTED_FEATURE.
	 *
	 * Additionally, the pcsc-lite headers incorrectly
	 * define SCARD_E_UNSUPPORTED_FEATURE to 0x8010001F,
	 * when the real value should be 0x80100022.
	 */
	if (errorCode != SCARD_S_SUCCESS)
	{
		if (errorCode == SCARD_E_UNEXPECTED)
			errorCode = SCARD_E_UNSUPPORTED_FEATURE;
	}

	return (LONG)errorCode;
}

WINPR_ATTR_NODISCARD
static DWORD PCSC_ConvertCardStateToWinSCard(DWORD dwCardState, PCSC_LONG status)
{
	/**
	 * pcsc-lite's SCardStatus returns a bit-field, not an enumerated value.
	 *
	 *         State             WinSCard           pcsc-lite
	 *
	 *      SCARD_UNKNOWN           0                0x0001
	 *      SCARD_ABSENT            1                0x0002
	 *      SCARD_PRESENT           2                0x0004
	 *      SCARD_SWALLOWED         3                0x0008
	 *      SCARD_POWERED           4                0x0010
	 *      SCARD_NEGOTIABLE        5                0x0020
	 *      SCARD_SPECIFIC          6                0x0040
	 *
	 * pcsc-lite also never sets SCARD_SPECIFIC,
	 * which is expected by some windows applications.
	 */
	if (status == SCARD_S_SUCCESS)
	{
		if ((dwCardState & PCSC_SCARD_NEGOTIABLE) || (dwCardState & PCSC_SCARD_SPECIFIC))
			return SCARD_SPECIFIC;
	}

	if (dwCardState & PCSC_SCARD_POWERED)
		return SCARD_POWERED;

	if (dwCardState & PCSC_SCARD_NEGOTIABLE)
		return SCARD_NEGOTIABLE;

	if (dwCardState & PCSC_SCARD_SPECIFIC)
		return SCARD_SPECIFIC;

	if (dwCardState & PCSC_SCARD_ABSENT)
		return SCARD_ABSENT;

	if (dwCardState & PCSC_SCARD_PRESENT)
		return SCARD_PRESENT;

	if (dwCardState & PCSC_SCARD_SWALLOWED)
		return SCARD_SWALLOWED;

	if (dwCardState & PCSC_SCARD_UNKNOWN)
		return SCARD_UNKNOWN;

	return SCARD_UNKNOWN;
}

WINPR_ATTR_NODISCARD
static DWORD PCSC_ConvertProtocolsToWinSCard(PCSC_DWORD dwProtocols)
{
	/**
	 * pcsc-lite uses a different value for SCARD_PROTOCOL_RAW,
	 * and also has SCARD_PROTOCOL_T15 which is not in WinSCard.
	 */
	if (dwProtocols & PCSC_SCARD_PROTOCOL_RAW)
	{
		dwProtocols &= ~PCSC_SCARD_PROTOCOL_RAW;
		dwProtocols |= SCARD_PROTOCOL_RAW;
	}

	if (dwProtocols & PCSC_SCARD_PROTOCOL_T15)
	{
		dwProtocols &= ~PCSC_SCARD_PROTOCOL_T15;
	}

	return (DWORD)dwProtocols;
}

WINPR_ATTR_NODISCARD
static DWORD PCSC_ConvertProtocolsFromWinSCard(DWORD dwProtocols)
{
	/**
	 * pcsc-lite uses a different value for SCARD_PROTOCOL_RAW,
	 * and it does not define WinSCard's SCARD_PROTOCOL_DEFAULT.
	 */
	if (dwProtocols & SCARD_PROTOCOL_RAW)
	{
		dwProtocols &= ~SCARD_PROTOCOL_RAW;
		dwProtocols |= PCSC_SCARD_PROTOCOL_RAW;
	}

	if (dwProtocols & SCARD_PROTOCOL_DEFAULT)
	{
		dwProtocols &= ~SCARD_PROTOCOL_DEFAULT;
	}

	if (dwProtocols == SCARD_PROTOCOL_UNDEFINED)
	{
		dwProtocols = SCARD_PROTOCOL_Tx;
	}

	return dwProtocols;
}

WINPR_ATTR_NODISCARD
static PCSC_SCARDCONTEXT* PCSC_GetCardContextData(SCARDCONTEXT hContext)
{
	if (!init())
		return nullptr;

	PCSC_SCARDCONTEXT* pContext =
	    (PCSC_SCARDCONTEXT*)ListDictionary_GetItemValue(g_CardContexts, (void*)hContext);

	if (!pContext)
		return nullptr;

	return pContext;
}

static void pcsc_cache_item_free(void* ptr)
{
	PCSC_CACHE_ITEM* data = ptr;
	if (data)
		free(data->data);
	free(data);
}

static PCSC_SCARDCONTEXT* PCSC_EstablishCardContext(SCARDCONTEXT hContext)
{
	if (!init())
		return nullptr;

	PCSC_SCARDCONTEXT* pContext = (PCSC_SCARDCONTEXT*)calloc(1, sizeof(PCSC_SCARDCONTEXT));

	if (!pContext)
		return nullptr;

	pContext->hContext = hContext;

	if (!InitializeCriticalSectionAndSpinCount(&(pContext->lock), 4000))
		goto errors;

	pContext->cache = HashTable_New(FALSE);
	if (!pContext->cache)
		goto errors;
	if (!HashTable_SetupForStringData(pContext->cache, FALSE))
		goto errors;
	{
		wObject* obj = HashTable_ValueObject(pContext->cache);
		obj->fnObjectFree = pcsc_cache_item_free;
	}

	if (!ListDictionary_Add(g_CardContexts, (void*)hContext, (void*)pContext))
		goto errors;

	return pContext;
errors:
	cardContextFree(pContext);
	return nullptr;
}

static void PCSC_ReleaseCardContext(SCARDCONTEXT hContext)
{
	if (init())
	{
		PCSC_SCARDCONTEXT* pContext = ListDictionary_Take(g_CardContexts, (void*)hContext);
		cardContextFree(pContext);
	}
}

WINPR_ATTR_NODISCARD
static BOOL PCSC_LockCardContext(SCARDCONTEXT hContext)
{
	PCSC_SCARDCONTEXT* pContext = nullptr;
	pContext = PCSC_GetCardContextData(hContext);

	if (!pContext)
	{
		WLog_ERR(TAG, "PCSC_LockCardContext: invalid context (%p)", (void*)hContext);
		return FALSE;
	}

	EnterCriticalSection(&(pContext->lock));
	return TRUE;
}

WINPR_ATTR_NODISCARD
static BOOL PCSC_UnlockCardContext(SCARDCONTEXT hContext)
{
	PCSC_SCARDCONTEXT* pContext = nullptr;
	pContext = PCSC_GetCardContextData(hContext);

	if (!pContext)
	{
		WLog_ERR(TAG, "PCSC_UnlockCardContext: invalid context (%p)", (void*)hContext);
		return FALSE;
	}

	LeaveCriticalSection(&(pContext->lock));
	return TRUE;
}

WINPR_ATTR_NODISCARD
static PCSC_SCARDHANDLE* PCSC_GetCardHandleData(SCARDHANDLE hCard)
{
	if (!init())
		return nullptr;

	PCSC_SCARDHANDLE* pCard =
	    (PCSC_SCARDHANDLE*)ListDictionary_GetItemValue(g_CardHandles, (void*)hCard);

	if (!pCard)
		return nullptr;

	return pCard;
}

WINPR_ATTR_NODISCARD
static SCARDCONTEXT PCSC_GetCardContextFromHandle(SCARDHANDLE hCard)
{
	PCSC_SCARDHANDLE* pCard = nullptr;
	pCard = PCSC_GetCardHandleData(hCard);

	if (!pCard)
		return 0;

	return pCard->hSharedContext;
}

static BOOL PCSC_WaitForCardAccess(SCARDCONTEXT hContext, SCARDHANDLE hCard, BOOL shared)
{
	BOOL status = TRUE;
	PCSC_SCARDHANDLE* pCard = nullptr;
	PCSC_SCARDCONTEXT* pContext = nullptr;

	if (!hCard)
	{
		/* SCardConnect */
		pContext = PCSC_GetCardContextData(hContext);

		if (!pContext)
			return FALSE;

		if (!pContext->owner)
			return TRUE;

		/* wait for card ownership */
		return TRUE;
	}

	pCard = PCSC_GetCardHandleData(hCard);

	if (!pCard)
		return FALSE;

	shared = pCard->shared;
	hContext = pCard->hSharedContext;
	pContext = PCSC_GetCardContextData(hContext);

	if (!pContext)
		return FALSE;

	if (!pContext->owner)
	{
		/* card is not owned */
		if (!shared)
			pContext->owner = hCard;

		return TRUE;
	}

	if (pContext->owner == hCard)
	{
		/* already card owner */
	}
	else
	{
		/* wait for card ownership */
	}

	return status;
}

static BOOL PCSC_ReleaseCardAccess(SCARDCONTEXT hContext, SCARDHANDLE hCard)
{
	PCSC_SCARDHANDLE* pCard = nullptr;
	PCSC_SCARDCONTEXT* pContext = nullptr;

	if (!hCard)
	{
		/* release current owner */
		pContext = PCSC_GetCardContextData(hContext);

		if (!pContext)
			return FALSE;

		hCard = pContext->owner;

		if (!hCard)
			return TRUE;

		pCard = PCSC_GetCardHandleData(hCard);

		if (!pCard)
			return FALSE;

		/* release card ownership */
		pContext->owner = 0;
		return TRUE;
	}

	pCard = PCSC_GetCardHandleData(hCard);

	if (!pCard)
		return FALSE;

	hContext = pCard->hSharedContext;
	pContext = PCSC_GetCardContextData(hContext);

	if (!pContext)
		return FALSE;

	if (pContext->owner == hCard)
	{
		/* release card ownership */
		pContext->owner = 0;
	}

	return TRUE;
}

static void PCSC_DisconnectCardHandle(PCSC_SCARDHANDLE* pCard)
{
	if (!pCard)
		return;

	PCSC_SCARDCONTEXT* pContext = PCSC_GetCardContextData(pCard->hSharedContext);
	if (init())
		ListDictionary_Remove(g_CardHandles, (void*)pCard);

	if (!pContext)
	{
		WLog_ERR(TAG, "PCSC_DisconnectCardHandle: null pContext!");
		return;
	}

	pContext->dwCardHandleCount--;
}

WINPR_ATTR_MALLOC(PCSC_DisconnectCardHandle, 1)
static PCSC_SCARDHANDLE* PCSC_ConnectCardHandle(SCARDCONTEXT hSharedContext, SCARDHANDLE hCard)
{
	if (!init())
		return nullptr;

	PCSC_SCARDCONTEXT* pContext = PCSC_GetCardContextData(hSharedContext);

	if (!pContext)
	{
		WLog_ERR(TAG, "PCSC_ConnectCardHandle: null pContext!");
		return nullptr;
	}

	PCSC_SCARDHANDLE* pCard = (PCSC_SCARDHANDLE*)calloc(1, sizeof(PCSC_SCARDHANDLE));

	if (!pCard)
		return nullptr;

	pCard->hSharedContext = hSharedContext;

	if (!ListDictionary_Add(g_CardHandles, (void*)hCard, (void*)pCard))
		goto error;

	pContext->dwCardHandleCount++;
	return pCard;
error:
	free(pCard);
	return nullptr;
}

static BOOL PCSC_AddMemoryBlock(SCARDCONTEXT hContext, void* pvMem)
{
	if (!init())
		return FALSE;
	return ListDictionary_Add(g_MemoryBlocks, pvMem, (void*)hContext);
}

WINPR_ATTR_NODISCARD
static void* PCSC_RemoveMemoryBlock(SCARDCONTEXT hContext, void* pvMem)
{
	WINPR_UNUSED(hContext);

	if (!init())
		return nullptr;

	return ListDictionary_Take(g_MemoryBlocks, pvMem);
}

/**
 * Standard Windows Smart Card API (PCSC)
 */
WINPR_ATTR_NODISCARD
static LONG WINAPI PCSC_SCardEstablishContext_Internal(DWORD dwScope, LPCVOID pvReserved1,
                                                       LPCVOID pvReserved2,
                                                       LPSCARDCONTEXT phContext)
{
	WINPR_UNUSED(dwScope); /* SCARD_SCOPE_SYSTEM is the only scope supported by pcsc-lite */
	PCSC_LONG status = SCARD_S_SUCCESS;

	if (!g_PCSC.pfnSCardEstablishContext)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardEstablishContext");

	status =
	    g_PCSC.pfnSCardEstablishContext(SCARD_SCOPE_SYSTEM, pvReserved1, pvReserved2, phContext);
	return PCSC_MapErrorCodeToWinSCard(status);
}

WINPR_ATTR_NODISCARD
static LONG WINAPI PCSC_SCardEstablishContext(DWORD dwScope, LPCVOID pvReserved1,
                                              LPCVOID pvReserved2, LPSCARDCONTEXT phContext)
{
	LONG status = 0;

	status = PCSC_SCardEstablishContext_Internal(dwScope, pvReserved1, pvReserved2, phContext);

	if (status == SCARD_S_SUCCESS)
		PCSC_EstablishCardContext(*phContext);

	return status;
}

WINPR_ATTR_NODISCARD
static LONG WINAPI PCSC_SCardReleaseContext_Internal(SCARDCONTEXT hContext)
{
	PCSC_LONG status = SCARD_S_SUCCESS;

	if (!g_PCSC.pfnSCardReleaseContext)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardReleaseContext");

	if (!hContext)
	{
		WLog_ERR(TAG, "SCardReleaseContext: null hContext");
		return PCSC_MapErrorCodeToWinSCard(SCARD_E_INVALID_HANDLE);
	}

	status = g_PCSC.pfnSCardReleaseContext(hContext);
	return PCSC_MapErrorCodeToWinSCard(status);
}

WINPR_ATTR_NODISCARD
static LONG WINAPI PCSC_SCardReleaseContext(SCARDCONTEXT hContext)
{
	LONG status = SCARD_S_SUCCESS;

	status = PCSC_SCardReleaseContext_Internal(hContext);
	PCSC_ReleaseCardContext(hContext);

	return status;
}

WINPR_ATTR_NODISCARD
static LONG WINAPI PCSC_SCardIsValidContext(SCARDCONTEXT hContext)
{
	PCSC_LONG status = SCARD_S_SUCCESS;

	if (!g_PCSC.pfnSCardIsValidContext)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardIsValidContext");

	status = g_PCSC.pfnSCardIsValidContext(hContext);
	return PCSC_MapErrorCodeToWinSCard(status);
}

WINPR_ATTR_NODISCARD
static LONG WINAPI PCSC_SCardListReaderGroups_Internal(SCARDCONTEXT hContext, LPSTR mszGroups,
                                                       LPDWORD pcchGroups)
{
	PCSC_LONG status = SCARD_S_SUCCESS;
	BOOL pcchGroupsAlloc = FALSE;
	PCSC_DWORD pcsc_cchGroups = 0;

	if (!pcchGroups)
		return SCARD_E_INVALID_PARAMETER;

	if (!g_PCSC.pfnSCardListReaderGroups)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardListReaderGroups");

	if (*pcchGroups == SCARD_AUTOALLOCATE)
		pcchGroupsAlloc = TRUE;

	pcsc_cchGroups = pcchGroupsAlloc ? PCSC_SCARD_AUTOALLOCATE : (PCSC_DWORD)*pcchGroups;

	if (pcchGroupsAlloc && !g_SCardAutoAllocate)
	{
		pcsc_cchGroups = 0;
		status = g_PCSC.pfnSCardListReaderGroups(hContext, nullptr, &pcsc_cchGroups);

		if (status == SCARD_S_SUCCESS)
		{
			LPSTR tmp = calloc(1, pcsc_cchGroups);

			if (!tmp)
				return SCARD_E_NO_MEMORY;

			status = g_PCSC.pfnSCardListReaderGroups(hContext, tmp, &pcsc_cchGroups);

			if (status != SCARD_S_SUCCESS)
			{
				free(tmp);
				tmp = nullptr;
			}
			else
				PCSC_AddMemoryBlock(hContext, tmp);

			*(LPSTR*)mszGroups = tmp;
		}
	}
	else
	{
		status = g_PCSC.pfnSCardListReaderGroups(hContext, mszGroups, &pcsc_cchGroups);
	}

	*pcchGroups = (DWORD)pcsc_cchGroups;
	return PCSC_MapErrorCodeToWinSCard(status);
}

WINPR_ATTR_NODISCARD
static LONG WINAPI PCSC_SCardListReaderGroupsA(SCARDCONTEXT hContext, LPSTR mszGroups,
                                               LPDWORD pcchGroups)
{
	LONG status = SCARD_S_SUCCESS;

	if (!g_PCSC.pfnSCardListReaderGroups)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardListReaderGroups");

	if (!PCSC_LockCardContext(hContext))
		return SCARD_E_INVALID_HANDLE;

	status = PCSC_SCardListReaderGroups_Internal(hContext, mszGroups, pcchGroups);

	if (!PCSC_UnlockCardContext(hContext))
		return SCARD_E_INVALID_HANDLE;

	return status;
}

WINPR_ATTR_NODISCARD
static LONG WINAPI PCSC_SCardListReaderGroupsW(SCARDCONTEXT hContext, LPWSTR mszGroups,
                                               LPDWORD pcchGroups)
{
	LPSTR mszGroupsA = nullptr;
	LPSTR* pMszGroupsA = &mszGroupsA;
	LONG status = SCARD_S_SUCCESS;

	if (!g_PCSC.pfnSCardListReaderGroups)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardListReaderGroups");

	if (!PCSC_LockCardContext(hContext))
		return SCARD_E_INVALID_HANDLE;

	status = PCSC_SCardListReaderGroups_Internal(hContext, (LPSTR)&mszGroupsA, pcchGroups);

	if (status == SCARD_S_SUCCESS)
	{
		size_t size = 0;
		WCHAR* str = ConvertMszUtf8NToWCharAlloc(*pMszGroupsA, *pcchGroups, &size);
		if (!str)
		{
			status = SCARD_E_NO_MEMORY;
			goto fail;
		}
		*(WCHAR**)mszGroups = str;
		*pcchGroups = (DWORD)size;
		PCSC_AddMemoryBlock(hContext, str);
		PCSC_SCardFreeMemory_Internal(hContext, *pMszGroupsA);
	}

fail:
	if (!PCSC_UnlockCardContext(hContext))
		return SCARD_E_INVALID_HANDLE;

	return status;
}

WINPR_ATTR_NODISCARD
static LONG WINAPI PCSC_SCardListReaders_Internal(SCARDCONTEXT hContext, LPCSTR mszGroups,
                                                  LPSTR mszReaders, LPDWORD pcchReaders)
{
	PCSC_LONG status = SCARD_S_SUCCESS;
	BOOL pcchReadersAlloc = FALSE;
	PCSC_DWORD pcsc_cchReaders = 0;
	if (!pcchReaders)
		return SCARD_E_INVALID_PARAMETER;

	if (!g_PCSC.pfnSCardListReaders)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardListReaders");

	mszGroups = nullptr; /* mszGroups is not supported by pcsc-lite */

	if (*pcchReaders == SCARD_AUTOALLOCATE)
		pcchReadersAlloc = TRUE;

	pcsc_cchReaders = pcchReadersAlloc ? PCSC_SCARD_AUTOALLOCATE : (PCSC_DWORD)*pcchReaders;

	if (pcchReadersAlloc && !g_SCardAutoAllocate)
	{
		pcsc_cchReaders = 0;
		status = g_PCSC.pfnSCardListReaders(hContext, mszGroups, nullptr, &pcsc_cchReaders);

		if (status == SCARD_S_SUCCESS)
		{
			char* tmp = calloc(1, pcsc_cchReaders);

			if (!tmp)
				return SCARD_E_NO_MEMORY;

			status = g_PCSC.pfnSCardListReaders(hContext, mszGroups, tmp, &pcsc_cchReaders);

			if (status != SCARD_S_SUCCESS)
			{
				free(tmp);
				tmp = nullptr;
			}
			else
				PCSC_AddMemoryBlock(hContext, tmp);

			*(char**)mszReaders = tmp;
		}
	}
	else
	{
		status = g_PCSC.pfnSCardListReaders(hContext, mszGroups, mszReaders, &pcsc_cchReaders);
	}

	*pcchReaders = (DWORD)pcsc_cchReaders;
	return PCSC_MapErrorCodeToWinSCard(status);
}

WINPR_ATTR_NODISCARD
static LONG WINAPI PCSC_SCardListReadersA(SCARDCONTEXT hContext, LPCSTR mszGroups, LPSTR mszReaders,
                                          LPDWORD pcchReaders)
{
	LONG status = SCARD_S_SUCCESS;
	BOOL nullCardContext = FALSE;

	if (!g_PCSC.pfnSCardListReaders)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardListReaders");

	if (!hContext)
	{
		status = PCSC_SCardEstablishContext(SCARD_SCOPE_SYSTEM, nullptr, nullptr, &hContext);

		if (status != SCARD_S_SUCCESS)

			return status;

		nullCardContext = TRUE;
	}

	if (!PCSC_LockCardContext(hContext))
	{
		status = SCARD_E_INVALID_HANDLE;
		goto release;
	}

	status = PCSC_SCardListReaders_Internal(hContext, mszGroups, mszReaders, pcchReaders);

	if (!PCSC_UnlockCardContext(hContext))
		status = SCARD_E_INVALID_HANDLE;

release:
	if (nullCardContext)
	{
		const LONG rc = PCSC_SCardReleaseContext(hContext);
		if (rc != SCARD_S_SUCCESS)
			status = rc;
	}

	return status;
}

WINPR_ATTR_NODISCARD
static LONG WINAPI PCSC_SCardListReadersW(SCARDCONTEXT hContext, LPCWSTR mszGroups,
                                          LPWSTR mszReaders, LPDWORD pcchReaders)
{
	LPSTR mszGroupsA = nullptr;
	LPSTR mszReadersA = nullptr;
	LONG status = SCARD_S_SUCCESS;
	BOOL nullCardContext = FALSE;

	if (!g_PCSC.pfnSCardListReaders)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardListReaders");

	if (!hContext)
	{
		status = PCSC_SCardEstablishContext(SCARD_SCOPE_SYSTEM, nullptr, nullptr, &hContext);

		if (status != SCARD_S_SUCCESS)

			return status;

		nullCardContext = TRUE;
	}

	if (!PCSC_LockCardContext(hContext))
	{
		status = SCARD_E_INVALID_HANDLE;
		goto release;
	}

	if (mszGroups)
	{
		mszGroupsA = ConvertWCharToUtf8Alloc(mszGroups, nullptr);
		if (!mszGroupsA)
		{
			status = SCARD_E_NO_MEMORY;
			goto fail;
		}
	}

	union
	{
		LPSTR* ppc;
		LPSTR pc;
	} cnv;
	cnv.ppc = &mszReadersA;

	status = PCSC_SCardListReaders_Internal(hContext, mszGroupsA, cnv.pc, pcchReaders);
	free(mszGroupsA);
	if (status == SCARD_S_SUCCESS)
	{
		size_t size = 0;
		WCHAR* str = ConvertMszUtf8NToWCharAlloc(mszReadersA, *pcchReaders, &size);
		PCSC_SCardFreeMemory_Internal(hContext, mszReadersA);
		if (!str || (size > UINT32_MAX))
		{
			status = SCARD_E_NO_MEMORY;
			free(str);
			goto fail;
		}

		*(LPWSTR*)mszReaders = str;
		*pcchReaders = (DWORD)size;
		PCSC_AddMemoryBlock(hContext, str);
	}

fail:
	if (!PCSC_UnlockCardContext(hContext))
		status = SCARD_E_INVALID_HANDLE;

release:
	if (nullCardContext)
	{
		const LONG rc = PCSC_SCardReleaseContext(hContext);
		if (rc != SCARD_S_SUCCESS)
			status = rc;
	}

	return status;
}

typedef struct
{
	BYTE atr[64];
	size_t atrLen;
	const char* cardName;
} PcscKnownAtr;

static PcscKnownAtr knownAtrs[] = {
	/* Yubico YubiKey 5 NFC (PKI) */
	{ { 0x3B, 0xFD, 0x13, 0x00, 0x00, 0x81, 0x31, 0xFE, 0x15, 0x80, 0x73, 0xC0,
	    0x21, 0xC0, 0x57, 0x59, 0x75, 0x62, 0x69, 0x4B, 0x65, 0x79, 0x40 },
	  23,
	  "NIST SP 800-73 [PIV]" },
	/* PIVKey C910 PKI Smart Card (eID) */
	{ { 0x3B, 0xFC, 0x18, 0x00, 0x00, 0x81, 0x31, 0x80, 0x45, 0x90, 0x67,
	    0x46, 0x4A, 0x00, 0x64, 0x16, 0x06, 0xF2, 0x72, 0x7E, 0x00, 0xE0 },
	  22,
	  "PIVKey Feitian (E0)" }
};

#ifndef ARRAY_LENGTH
#define ARRAY_LENGTH(a) (sizeof(a) / sizeof(a)[0])
#endif

WINPR_ATTR_NODISCARD
static const char* findCardByAtr(LPCBYTE pbAtr)
{
	for (size_t i = 0; i < ARRAY_LENGTH(knownAtrs); i++)
	{
		if (memcmp(knownAtrs[i].atr, pbAtr, knownAtrs[i].atrLen) == 0)
			return knownAtrs[i].cardName;
	}

	return nullptr;
}

WINPR_ATTR_NODISCARD
static LONG WINAPI PCSC_SCardListCardsA(WINPR_ATTR_UNUSED SCARDCONTEXT hContext, LPCBYTE pbAtr,
                                        LPCGUID rgquidInterfaces, DWORD cguidInterfaceCount,
                                        CHAR* mszCards, LPDWORD pcchCards)
{
	const char* cardName = nullptr;
	DWORD outputLen = 1;
	CHAR* output = nullptr;
	BOOL autoAllocate = 0;

	if (!pbAtr || rgquidInterfaces || cguidInterfaceCount)
		return SCARD_E_UNSUPPORTED_FEATURE;

	if (!pcchCards)
		return SCARD_E_INVALID_PARAMETER;

	autoAllocate = (*pcchCards == SCARD_AUTOALLOCATE);

	cardName = findCardByAtr(pbAtr);
	if (cardName)
		outputLen += strlen(cardName) + 1;

	*pcchCards = outputLen;
	if (autoAllocate)
	{
		output = malloc(outputLen);
		if (!output)
			return SCARD_E_NO_MEMORY;

		*((LPSTR*)mszCards) = output;
	}
	else
	{
		if (!mszCards)
			return SCARD_S_SUCCESS;

		if (*pcchCards < outputLen)
			return SCARD_E_INSUFFICIENT_BUFFER;

		output = mszCards;
	}

	if (cardName)
	{
		size_t toCopy = strlen(cardName) + 1;
		memcpy(output, cardName, toCopy);
		output += toCopy;
	}

	*output = '\0';

	return SCARD_S_SUCCESS;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardListCardsW(
    WINPR_ATTR_UNUSED SCARDCONTEXT hContext, LPCBYTE pbAtr, LPCGUID rgquidInterfaces,
    DWORD cguidInterfaceCount, WCHAR* mszCards, LPDWORD pcchCards)
{
	const char* cardName = nullptr;
	DWORD outputLen = 1;
	WCHAR* output = nullptr;
	BOOL autoAllocate = 0;

	if (!pbAtr || rgquidInterfaces || cguidInterfaceCount)
		return SCARD_E_UNSUPPORTED_FEATURE;

	if (!pcchCards)
		return SCARD_E_INVALID_PARAMETER;

	autoAllocate = (*pcchCards == SCARD_AUTOALLOCATE);

	cardName = findCardByAtr(pbAtr);
	if (cardName)
		outputLen += strlen(cardName) + 1;

	*pcchCards = outputLen;
	if (autoAllocate)
	{
		output = calloc(outputLen, sizeof(WCHAR));
		if (!output)
			return SCARD_E_NO_MEMORY;

		*((LPWSTR*)mszCards) = output;
	}
	else
	{
		if (!mszCards)
			return SCARD_S_SUCCESS;

		if (*pcchCards < outputLen)
			return SCARD_E_INSUFFICIENT_BUFFER;

		output = mszCards;
	}

	if (cardName)
	{
		size_t toCopy = strlen(cardName) + 1;
		if (ConvertUtf8ToWChar(cardName, output, toCopy) < 0)
			return SCARD_F_INTERNAL_ERROR;
		output += toCopy;
	}

	*output = 0;

	return SCARD_S_SUCCESS;
}

WINPR_ATTR_NODISCARD
static LONG WINAPI
PCSC_SCardListInterfacesA(SCARDCONTEXT hContext, LPCSTR szCard, LPGUID pguidInterfaces,
                          LPDWORD pcguidInterfaces /* NOLINT(readability-non-const-parameter) */)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szCard);
	WINPR_UNUSED(pguidInterfaces);
	WINPR_UNUSED(pcguidInterfaces);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD
static LONG WINAPI
PCSC_SCardListInterfacesW(SCARDCONTEXT hContext, LPCWSTR szCard, LPGUID pguidInterfaces,
                          LPDWORD pcguidInterfaces /* NOLINT(readability-non-const-parameter) */)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szCard);
	WINPR_UNUSED(pguidInterfaces);
	WINPR_UNUSED(pcguidInterfaces);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardGetProviderIdA(SCARDCONTEXT hContext,
                                                                 LPCSTR szCard,
                                                                 LPGUID pguidProviderId)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szCard);
	WINPR_UNUSED(pguidProviderId);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardGetProviderIdW(SCARDCONTEXT hContext,
                                                                 LPCWSTR szCard,
                                                                 LPGUID pguidProviderId)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szCard);
	WINPR_UNUSED(pguidProviderId);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardGetCardTypeProviderNameA(
    SCARDCONTEXT hContext, LPCSTR szCardName, DWORD dwProviderId,
    CHAR* szProvider /* NOLINT(readability-non-const-parameter) */,
    LPDWORD pcchProvider /* NOLINT(readability-non-const-parameter) */)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szCardName);
	WINPR_UNUSED(dwProviderId);
	WINPR_UNUSED(szProvider);
	WINPR_UNUSED(pcchProvider);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardGetCardTypeProviderNameW(
    SCARDCONTEXT hContext, LPCWSTR szCardName, DWORD dwProviderId,
    WCHAR* szProvider /* NOLINT(readability-non-const-parameter) */,
    LPDWORD pcchProvider /* NOLINT(readability-non-const-parameter) */)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szCardName);
	WINPR_UNUSED(dwProviderId);
	WINPR_UNUSED(szProvider);
	WINPR_UNUSED(pcchProvider);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardIntroduceReaderGroupA(SCARDCONTEXT hContext,
                                                                        LPCSTR szGroupName)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szGroupName);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardIntroduceReaderGroupW(SCARDCONTEXT hContext,
                                                                        LPCWSTR szGroupName)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szGroupName);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardForgetReaderGroupA(SCARDCONTEXT hContext,
                                                                     LPCSTR szGroupName)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szGroupName);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardForgetReaderGroupW(SCARDCONTEXT hContext,
                                                                     LPCWSTR szGroupName)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szGroupName);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardIntroduceReaderA(SCARDCONTEXT hContext,
                                                                   LPCSTR szReaderName,
                                                                   LPCSTR szDeviceName)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szReaderName);
	WINPR_UNUSED(szDeviceName);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardIntroduceReaderW(SCARDCONTEXT hContext,
                                                                   LPCWSTR szReaderName,
                                                                   LPCWSTR szDeviceName)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szReaderName);
	WINPR_UNUSED(szDeviceName);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardForgetReaderA(SCARDCONTEXT hContext,
                                                                LPCSTR szReaderName)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szReaderName);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardForgetReaderW(SCARDCONTEXT hContext,
                                                                LPCWSTR szReaderName)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szReaderName);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardAddReaderToGroupA(SCARDCONTEXT hContext,
                                                                    LPCSTR szReaderName,
                                                                    LPCSTR szGroupName)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szReaderName);
	WINPR_UNUSED(szGroupName);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardAddReaderToGroupW(SCARDCONTEXT hContext,
                                                                    LPCWSTR szReaderName,
                                                                    LPCWSTR szGroupName)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szReaderName);
	WINPR_UNUSED(szGroupName);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardRemoveReaderFromGroupA(SCARDCONTEXT hContext,
                                                                         LPCSTR szReaderName,
                                                                         LPCSTR szGroupName)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szReaderName);
	WINPR_UNUSED(szGroupName);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardRemoveReaderFromGroupW(SCARDCONTEXT hContext,
                                                                         LPCWSTR szReaderName,
                                                                         LPCWSTR szGroupName)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szReaderName);
	WINPR_UNUSED(szGroupName);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI
PCSC_SCardIntroduceCardTypeA(SCARDCONTEXT hContext, LPCSTR szCardName, LPCGUID pguidPrimaryProvider,
                             LPCGUID rgguidInterfaces, DWORD dwInterfaceCount, LPCBYTE pbAtr,
                             LPCBYTE pbAtrMask, DWORD cbAtrLen)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szCardName);
	WINPR_UNUSED(pguidPrimaryProvider);
	WINPR_UNUSED(rgguidInterfaces);
	WINPR_UNUSED(dwInterfaceCount);
	WINPR_UNUSED(pbAtr);
	WINPR_UNUSED(pbAtrMask);
	WINPR_UNUSED(cbAtrLen);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardIntroduceCardTypeW(
    SCARDCONTEXT hContext, LPCWSTR szCardName, LPCGUID pguidPrimaryProvider,
    LPCGUID rgguidInterfaces, DWORD dwInterfaceCount, LPCBYTE pbAtr, LPCBYTE pbAtrMask,
    DWORD cbAtrLen)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szCardName);
	WINPR_UNUSED(pguidPrimaryProvider);
	WINPR_UNUSED(rgguidInterfaces);
	WINPR_UNUSED(dwInterfaceCount);
	WINPR_UNUSED(pbAtr);
	WINPR_UNUSED(pbAtrMask);
	WINPR_UNUSED(cbAtrLen);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardSetCardTypeProviderNameA(SCARDCONTEXT hContext,
                                                                           LPCSTR szCardName,
                                                                           DWORD dwProviderId,
                                                                           LPCSTR szProvider)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szCardName);
	WINPR_UNUSED(dwProviderId);
	WINPR_UNUSED(szProvider);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardSetCardTypeProviderNameW(SCARDCONTEXT hContext,
                                                                           LPCWSTR szCardName,
                                                                           DWORD dwProviderId,
                                                                           LPCWSTR szProvider)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szCardName);
	WINPR_UNUSED(dwProviderId);
	WINPR_UNUSED(szProvider);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardForgetCardTypeA(SCARDCONTEXT hContext,
                                                                  LPCSTR szCardName)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szCardName);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardForgetCardTypeW(SCARDCONTEXT hContext,
                                                                  LPCWSTR szCardName)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szCardName);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

static LONG WINAPI PCSC_SCardFreeMemory_Internal(SCARDCONTEXT hContext, LPVOID pvMem)
{
	PCSC_LONG status = SCARD_S_SUCCESS;

	if (PCSC_RemoveMemoryBlock(hContext, pvMem))
		status = SCARD_S_SUCCESS;
	else
	{
		if (g_PCSC.pfnSCardFreeMemory)
		{
			status = g_PCSC.pfnSCardFreeMemory(hContext, pvMem);
		}
	}

	return PCSC_MapErrorCodeToWinSCard(status);
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardFreeMemory(SCARDCONTEXT hContext, LPVOID pvMem)
{
	LONG status = SCARD_S_SUCCESS;

	if (hContext)
	{
		if (!PCSC_LockCardContext(hContext))
			return SCARD_E_INVALID_HANDLE;
	}

	status = PCSC_SCardFreeMemory_Internal(hContext, pvMem);

	if (hContext)
	{
		if (!PCSC_UnlockCardContext(hContext))
			return SCARD_E_INVALID_HANDLE;
	}

	return status;
}

WINPR_ATTR_NODISCARD
static HANDLE WINAPI PCSC_SCardAccessStartedEvent(void)
{
	LONG status = 0;
	SCARDCONTEXT hContext = 0;

	status = PCSC_SCardEstablishContext(SCARD_SCOPE_SYSTEM, nullptr, nullptr, &hContext);

	if (status != SCARD_S_SUCCESS)
		return nullptr;

	status = PCSC_SCardReleaseContext(hContext);

	if (status != SCARD_S_SUCCESS)
		return nullptr;

	if (!g_StartedEvent)
	{
		if (!(g_StartedEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr)))
			return nullptr;

		if (!SetEvent(g_StartedEvent))
		{
			(void)CloseHandle(g_StartedEvent);
			return nullptr;
		}
	}

	g_StartedEventRefCount++;
	return g_StartedEvent;
}

static void WINAPI PCSC_SCardReleaseStartedEvent(void)
{
	g_StartedEventRefCount--;

	if (g_StartedEventRefCount == 0)
	{
		if (g_StartedEvent)
		{
			(void)CloseHandle(g_StartedEvent);
			g_StartedEvent = nullptr;
		}
	}
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardLocateCardsA(SCARDCONTEXT hContext,
                                                               LPCSTR mszCards,
                                                               LPSCARD_READERSTATEA rgReaderStates,
                                                               DWORD cReaders)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(mszCards);
	WINPR_UNUSED(rgReaderStates);
	WINPR_UNUSED(cReaders);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardLocateCardsW(SCARDCONTEXT hContext,
                                                               LPCWSTR mszCards,
                                                               LPSCARD_READERSTATEW rgReaderStates,
                                                               DWORD cReaders)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(mszCards);
	WINPR_UNUSED(rgReaderStates);
	WINPR_UNUSED(cReaders);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI
PCSC_SCardLocateCardsByATRA(SCARDCONTEXT hContext, LPSCARD_ATRMASK rgAtrMasks, DWORD cAtrs,
                            LPSCARD_READERSTATEA rgReaderStates, DWORD cReaders)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(rgAtrMasks);
	WINPR_UNUSED(cAtrs);
	WINPR_UNUSED(rgReaderStates);
	WINPR_UNUSED(cReaders);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI
PCSC_SCardLocateCardsByATRW(SCARDCONTEXT hContext, LPSCARD_ATRMASK rgAtrMasks, DWORD cAtrs,
                            LPSCARD_READERSTATEW rgReaderStates, DWORD cReaders)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(rgAtrMasks);
	WINPR_UNUSED(cAtrs);
	WINPR_UNUSED(rgReaderStates);
	WINPR_UNUSED(cReaders);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardGetStatusChange_Internal(
    SCARDCONTEXT hContext, DWORD dwTimeout, LPSCARD_READERSTATEA rgReaderStates, DWORD cReaders)
{
	INT64* map = nullptr;
	PCSC_DWORD cMappedReaders = 0;
	PCSC_SCARD_READERSTATE* states = nullptr;
	PCSC_LONG status = SCARD_S_SUCCESS;
	PCSC_DWORD pcsc_dwTimeout = (PCSC_DWORD)dwTimeout;
	PCSC_DWORD pcsc_cReaders = (PCSC_DWORD)cReaders;

	if (!g_PCSC.pfnSCardGetStatusChange)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardGetStatusChange");

	if (!cReaders)
		return SCARD_S_SUCCESS;

	/* pcsc-lite interprets value 0 as INFINITE, work around the problem by using value 1 */
	pcsc_dwTimeout = pcsc_dwTimeout ? pcsc_dwTimeout : 1;
	/**
	 * Apple's SmartCard Services (not vanilla pcsc-lite) appears to have trouble with the
	 * "\\\\?PnP?\\Notification" reader name. I am always getting EXC_BAD_ACCESS with it.
	 *
	 * The SmartCard Services tarballs can be found here:
	 * http://opensource.apple.com/tarballs/SmartCardServices/
	 *
	 * The "\\\\?PnP?\\Notification" string cannot be found anywhere in the sources,
	 * while this string is present in the vanilla pcsc-lite sources.
	 *
	 * To work around this apparent lack of "\\\\?PnP?\\Notification" support,
	 * we have to filter rgReaderStates to exclude the special PnP reader name.
	 */
	map = (INT64*)calloc(pcsc_cReaders, sizeof(INT64));

	if (!map)
		return SCARD_E_NO_MEMORY;

	states = (PCSC_SCARD_READERSTATE*)calloc(pcsc_cReaders, sizeof(PCSC_SCARD_READERSTATE));

	if (!states)
	{
		free(map);
		return SCARD_E_NO_MEMORY;
	}

	PCSC_DWORD j = 0;
	for (PCSC_DWORD i = 0; i < pcsc_cReaders; i++)
	{
		if (!g_PnP_Notification)
		{
			LPSCARD_READERSTATEA reader = &rgReaderStates[i];
			if (!reader->szReader)
				continue;
			if (0 == _stricmp(reader->szReader, SMARTCARD_PNP_NOTIFICATION_A))
			{
				map[i] = -1; /* unmapped */
				continue;
			}
		}

		map[i] = (INT64)j;
		states[j].szReader = rgReaderStates[i].szReader;
		states[j].dwCurrentState = rgReaderStates[i].dwCurrentState;
		states[j].pvUserData = rgReaderStates[i].pvUserData;
		states[j].dwEventState = rgReaderStates[i].dwEventState;
		states[j].cbAtr = rgReaderStates[i].cbAtr;
		CopyMemory(&(states[j].rgbAtr), &(rgReaderStates[i].rgbAtr), PCSC_MAX_ATR_SIZE);
		j++;
	}

	cMappedReaders = j;

	if (cMappedReaders > 0)
	{
		status = g_PCSC.pfnSCardGetStatusChange(hContext, pcsc_dwTimeout, states, cMappedReaders);
	}
	else
	{
		status = SCARD_S_SUCCESS;
	}

	for (PCSC_DWORD i = 0; i < pcsc_cReaders; i++)
	{
		if (map[i] < 0)
			continue; /* unmapped */

		PCSC_DWORD k = (PCSC_DWORD)map[i];
		rgReaderStates[i].dwCurrentState = (DWORD)states[k].dwCurrentState;
		rgReaderStates[i].cbAtr = (DWORD)states[k].cbAtr;
		CopyMemory(&(rgReaderStates[i].rgbAtr), &(states[k].rgbAtr), PCSC_MAX_ATR_SIZE);
		rgReaderStates[i].dwEventState = (DWORD)states[k].dwEventState;
	}

	free(map);
	free(states);
	return PCSC_MapErrorCodeToWinSCard(status);
}

WINPR_ATTR_NODISCARD
static LONG WINAPI PCSC_SCardGetStatusChangeA(SCARDCONTEXT hContext, DWORD dwTimeout,
                                              LPSCARD_READERSTATEA rgReaderStates, DWORD cReaders)
{
	LONG status = SCARD_S_SUCCESS;

	if (!PCSC_LockCardContext(hContext))
		return SCARD_E_INVALID_HANDLE;

	status = PCSC_SCardGetStatusChange_Internal(hContext, dwTimeout, rgReaderStates, cReaders);

	if (!PCSC_UnlockCardContext(hContext))
		return SCARD_E_INVALID_HANDLE;

	return status;
}

WINPR_ATTR_NODISCARD
static LONG WINAPI PCSC_SCardGetStatusChangeW(SCARDCONTEXT hContext, DWORD dwTimeout,
                                              LPSCARD_READERSTATEW rgReaderStates, DWORD cReaders)
{
	LPSCARD_READERSTATEA states = nullptr;
	LONG status = SCARD_S_SUCCESS;

	if (!g_PCSC.pfnSCardGetStatusChange)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardGetStatusChange");

	if (!PCSC_LockCardContext(hContext))
		return SCARD_E_INVALID_HANDLE;

	states = (LPSCARD_READERSTATEA)calloc(cReaders, sizeof(SCARD_READERSTATEA));

	if (!states)
	{
		if (!PCSC_UnlockCardContext(hContext))
			return SCARD_E_INVALID_HANDLE;
		return SCARD_E_NO_MEMORY;
	}

	for (DWORD index = 0; index < cReaders; index++)
	{
		const LPSCARD_READERSTATEW curReader = &rgReaderStates[index];
		LPSCARD_READERSTATEA cur = &states[index];

		cur->szReader = ConvertWCharToUtf8Alloc(curReader->szReader, nullptr);
		cur->pvUserData = curReader->pvUserData;
		cur->dwCurrentState = curReader->dwCurrentState;
		cur->dwEventState = curReader->dwEventState;
		cur->cbAtr = curReader->cbAtr;
		CopyMemory(&(cur->rgbAtr), &(curReader->rgbAtr), ARRAYSIZE(cur->rgbAtr));
	}

	status = PCSC_SCardGetStatusChange_Internal(hContext, dwTimeout, states, cReaders);

	for (DWORD index = 0; index < cReaders; index++)
	{
		free((void*)states[index].szReader);
		rgReaderStates[index].pvUserData = states[index].pvUserData;
		rgReaderStates[index].dwCurrentState = states[index].dwCurrentState;
		rgReaderStates[index].dwEventState = states[index].dwEventState;
		rgReaderStates[index].cbAtr = states[index].cbAtr;
		CopyMemory(&(rgReaderStates[index].rgbAtr), &(states[index].rgbAtr), 36);
	}

	free(states);

	if (!PCSC_UnlockCardContext(hContext))
		return SCARD_E_INVALID_HANDLE;

	return status;
}

static LONG WINAPI PCSC_SCardCancel(SCARDCONTEXT hContext)
{
	PCSC_LONG status = SCARD_S_SUCCESS;

	if (!g_PCSC.pfnSCardCancel)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardCancel");

	status = g_PCSC.pfnSCardCancel(hContext);
	return PCSC_MapErrorCodeToWinSCard(status);
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardConnect_Internal(
    SCARDCONTEXT hContext, LPCSTR szReader, DWORD dwShareMode, DWORD dwPreferredProtocols,
    LPSCARDHANDLE phCard, LPDWORD pdwActiveProtocol)
{
	BOOL shared = 0;
	const char* szReaderPCSC = nullptr;
	PCSC_LONG status = SCARD_S_SUCCESS;
	PCSC_SCARDHANDLE* pCard = nullptr;
	PCSC_DWORD pcsc_dwShareMode = (PCSC_DWORD)dwShareMode;
	PCSC_DWORD pcsc_dwPreferredProtocols = 0;
	PCSC_DWORD pcsc_dwActiveProtocol = 0;

	if (!g_PCSC.pfnSCardConnect)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardConnect");

	shared = (dwShareMode == SCARD_SHARE_DIRECT) != 0;
	PCSC_WaitForCardAccess(hContext, 0, shared);
	szReaderPCSC = szReader;

	/**
	 * As stated here :
	 * https://pcsclite.alioth.debian.org/api/group__API.html#ga4e515829752e0a8dbc4d630696a8d6a5
	 * SCARD_PROTOCOL_UNDEFINED is valid for dwPreferredProtocols (only) if dwShareMode ==
	 * SCARD_SHARE_DIRECT and allows to send control commands to the reader (with SCardControl())
	 * even if a card is not present in the reader
	 */
	if (pcsc_dwShareMode == SCARD_SHARE_DIRECT && dwPreferredProtocols == SCARD_PROTOCOL_UNDEFINED)
		pcsc_dwPreferredProtocols = SCARD_PROTOCOL_UNDEFINED;
	else
		pcsc_dwPreferredProtocols =
		    (PCSC_DWORD)PCSC_ConvertProtocolsFromWinSCard(dwPreferredProtocols);

	status = g_PCSC.pfnSCardConnect(hContext, szReaderPCSC, pcsc_dwShareMode,
	                                pcsc_dwPreferredProtocols, phCard, &pcsc_dwActiveProtocol);

	if (status == SCARD_S_SUCCESS)
	{
		pCard = PCSC_ConnectCardHandle(hContext, *phCard);
		*pdwActiveProtocol = PCSC_ConvertProtocolsToWinSCard((DWORD)pcsc_dwActiveProtocol);
		pCard->shared = shared;

		// NOLINTNEXTLINE(clang-analyzer-unix.Malloc): ListDictionary_Add takes ownership of pCard
		PCSC_WaitForCardAccess(hContext, pCard->hSharedContext, shared);
	}

	return PCSC_MapErrorCodeToWinSCard(status);
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardConnectA(SCARDCONTEXT hContext, LPCSTR szReader,
                                                           DWORD dwShareMode,
                                                           DWORD dwPreferredProtocols,
                                                           LPSCARDHANDLE phCard,
                                                           LPDWORD pdwActiveProtocol)
{
	LONG status = SCARD_S_SUCCESS;

	if (!PCSC_LockCardContext(hContext))
		return SCARD_E_INVALID_HANDLE;

	status = PCSC_SCardConnect_Internal(hContext, szReader, dwShareMode, dwPreferredProtocols,
	                                    phCard, pdwActiveProtocol);

	if (!PCSC_UnlockCardContext(hContext))
		return SCARD_E_INVALID_HANDLE;

	return status;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardConnectW(SCARDCONTEXT hContext, LPCWSTR szReader,
                                                           DWORD dwShareMode,
                                                           DWORD dwPreferredProtocols,
                                                           LPSCARDHANDLE phCard,
                                                           LPDWORD pdwActiveProtocol)
{
	LPSTR szReaderA = nullptr;
	LONG status = SCARD_S_SUCCESS;

	if (!PCSC_LockCardContext(hContext))
		return SCARD_E_INVALID_HANDLE;

	if (szReader)
	{
		szReaderA = ConvertWCharToUtf8Alloc(szReader, nullptr);
		if (!szReaderA)
		{
			status = SCARD_E_INSUFFICIENT_BUFFER;
			goto fail;
		}
	}

	status = PCSC_SCardConnect_Internal(hContext, szReaderA, dwShareMode, dwPreferredProtocols,
	                                    phCard, pdwActiveProtocol);
	free(szReaderA);

fail:
	if (!PCSC_UnlockCardContext(hContext))
		return SCARD_E_INVALID_HANDLE;

	return status;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardReconnect(SCARDHANDLE hCard, DWORD dwShareMode,
                                                            DWORD dwPreferredProtocols,
                                                            DWORD dwInitialization,
                                                            LPDWORD pdwActiveProtocol)
{
	PCSC_LONG status = SCARD_S_SUCCESS;
	PCSC_DWORD pcsc_dwShareMode = (PCSC_DWORD)dwShareMode;
	PCSC_DWORD pcsc_dwPreferredProtocols = 0;
	PCSC_DWORD pcsc_dwInitialization = (PCSC_DWORD)dwInitialization;
	PCSC_DWORD pcsc_dwActiveProtocol = 0;

	if (!g_PCSC.pfnSCardReconnect)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardReconnect");

	const BOOL shared = (dwShareMode == SCARD_SHARE_DIRECT) != 0;
	PCSC_WaitForCardAccess(0, hCard, shared);
	pcsc_dwPreferredProtocols = (PCSC_DWORD)PCSC_ConvertProtocolsFromWinSCard(dwPreferredProtocols);
	status = g_PCSC.pfnSCardReconnect(hCard, pcsc_dwShareMode, pcsc_dwPreferredProtocols,
	                                  pcsc_dwInitialization, &pcsc_dwActiveProtocol);

	*pdwActiveProtocol = PCSC_ConvertProtocolsToWinSCard((DWORD)pcsc_dwActiveProtocol);
	return PCSC_MapErrorCodeToWinSCard(status);
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardDisconnect(SCARDHANDLE hCard, DWORD dwDisposition)
{
	PCSC_LONG status = SCARD_S_SUCCESS;
	PCSC_DWORD pcsc_dwDisposition = (PCSC_DWORD)dwDisposition;

	if (!g_PCSC.pfnSCardDisconnect)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardDisconnect");

	status = g_PCSC.pfnSCardDisconnect(hCard, pcsc_dwDisposition);

	if (status == SCARD_S_SUCCESS)
	{
		PCSC_SCARDHANDLE* pCard = PCSC_GetCardHandleData(hCard);
		PCSC_DisconnectCardHandle(pCard);
	}

	PCSC_ReleaseCardAccess(0, hCard);
	return PCSC_MapErrorCodeToWinSCard(status);
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardBeginTransaction(SCARDHANDLE hCard)
{
	PCSC_LONG status = SCARD_S_SUCCESS;
	PCSC_SCARDHANDLE* pCard = nullptr;
	PCSC_SCARDCONTEXT* pContext = nullptr;

	if (!g_PCSC.pfnSCardBeginTransaction)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardBeginTransaction");

	pCard = PCSC_GetCardHandleData(hCard);

	if (!pCard)
		return SCARD_E_INVALID_HANDLE;

	pContext = PCSC_GetCardContextData(pCard->hSharedContext);

	if (!pContext)
		return SCARD_E_INVALID_HANDLE;

	if (pContext->isTransactionLocked)
		return SCARD_S_SUCCESS; /* disable nested transactions */

	status = g_PCSC.pfnSCardBeginTransaction(hCard);

	pContext->isTransactionLocked = TRUE;
	return PCSC_MapErrorCodeToWinSCard(status);
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardEndTransaction(SCARDHANDLE hCard,
                                                                 DWORD dwDisposition)
{
	PCSC_LONG status = SCARD_S_SUCCESS;
	PCSC_SCARDHANDLE* pCard = nullptr;
	PCSC_SCARDCONTEXT* pContext = nullptr;
	PCSC_DWORD pcsc_dwDisposition = (PCSC_DWORD)dwDisposition;

	if (!g_PCSC.pfnSCardEndTransaction)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardEndTransaction");

	pCard = PCSC_GetCardHandleData(hCard);

	if (!pCard)
		return SCARD_E_INVALID_HANDLE;

	pContext = PCSC_GetCardContextData(pCard->hSharedContext);

	if (!pContext)
		return SCARD_E_INVALID_HANDLE;

	PCSC_ReleaseCardAccess(0, hCard);

	if (!pContext->isTransactionLocked)
		return SCARD_S_SUCCESS; /* disable nested transactions */

	status = g_PCSC.pfnSCardEndTransaction(hCard, pcsc_dwDisposition);

	pContext->isTransactionLocked = FALSE;
	return PCSC_MapErrorCodeToWinSCard(status);
}

static LONG WINAPI PCSC_SCardCancelTransaction(SCARDHANDLE hCard)
{
	WINPR_UNUSED(hCard);
	return SCARD_S_SUCCESS;
}

/*
 * PCSC returns a string but Windows SCardStatus requires the return to be a multi string.
 * Therefore extra length checks and additional buffer allocation is required
 */
WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardStatus_Internal(
    SCARDHANDLE hCard, LPSTR mszReaderNames, LPDWORD pcchReaderLen, LPDWORD pdwState,
    LPDWORD pdwProtocol, LPBYTE pbAtr, LPDWORD pcbAtrLen, BOOL unicode)
{
	PCSC_SCARDHANDLE* pCard = nullptr;
	SCARDCONTEXT hContext = 0;
	PCSC_LONG status = 0;
	PCSC_DWORD pcsc_cchReaderLen = 0;
	PCSC_DWORD pcsc_cbAtrLen = 0;
	PCSC_DWORD pcsc_dwState = 0;
	PCSC_DWORD pcsc_dwProtocol = 0;
	BOOL allocateReader = FALSE;
	BOOL allocateAtr = FALSE;
	LPSTR readerNames = mszReaderNames;
	LPBYTE atr = pbAtr;
	LPSTR tReader = nullptr;
	LPBYTE tATR = nullptr;

	if (!g_PCSC.pfnSCardStatus)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardStatus");

	pCard = PCSC_GetCardHandleData(hCard);

	if (!pCard)
		return SCARD_E_INVALID_VALUE;

	PCSC_WaitForCardAccess(0, hCard, pCard->shared);
	hContext = PCSC_GetCardContextFromHandle(hCard);

	if (!hContext)
		return SCARD_E_INVALID_VALUE;

	status = g_PCSC.pfnSCardStatus(hCard, nullptr, &pcsc_cchReaderLen, nullptr, nullptr, nullptr,
	                               &pcsc_cbAtrLen);

	if (status != STATUS_SUCCESS)
		return PCSC_MapErrorCodeToWinSCard(status);

	pcsc_cchReaderLen++;

	if (unicode)
		pcsc_cchReaderLen *= 2;

	if (pcchReaderLen)
	{
		if (*pcchReaderLen == SCARD_AUTOALLOCATE)
			allocateReader = TRUE;
		else if (mszReaderNames && (*pcchReaderLen < pcsc_cchReaderLen))
			return SCARD_E_INSUFFICIENT_BUFFER;
		else
			pcsc_cchReaderLen = *pcchReaderLen;
	}

	if (pcbAtrLen)
	{
		if (*pcbAtrLen == SCARD_AUTOALLOCATE)
			allocateAtr = TRUE;
		else if (pbAtr && (*pcbAtrLen < pcsc_cbAtrLen))
			return SCARD_E_INSUFFICIENT_BUFFER;
		else
			pcsc_cbAtrLen = *pcbAtrLen;
	}

	if (allocateReader && pcsc_cchReaderLen > 0 && mszReaderNames)
	{
#ifdef __MACOSX__

		/**
		 * Workaround for SCardStatus Bug in MAC OS X Yosemite
		 */
		if (OSXVersion == 0x10100000)
			pcsc_cchReaderLen++;

#endif
		tReader = calloc(sizeof(CHAR), pcsc_cchReaderLen + 1);

		if (!tReader)
		{
			status = ERROR_NOT_ENOUGH_MEMORY;
			goto out_fail;
		}

		readerNames = tReader;
	}

	if (allocateAtr && pcsc_cbAtrLen > 0 && pbAtr)
	{
		tATR = calloc(1, pcsc_cbAtrLen);

		if (!tATR)
		{
			status = ERROR_NOT_ENOUGH_MEMORY;
			goto out_fail;
		}

		atr = tATR;
	}

	status = g_PCSC.pfnSCardStatus(hCard, readerNames, &pcsc_cchReaderLen, &pcsc_dwState,
	                               &pcsc_dwProtocol, atr, &pcsc_cbAtrLen);

	if (status != STATUS_SUCCESS)
		goto out_fail;

	if (tATR)
	{
		PCSC_AddMemoryBlock(hContext, tATR);
		*(BYTE**)pbAtr = tATR;
	}

	if (tReader)
	{
		if (unicode)
		{
			size_t size = 0;
			WCHAR* tmp = ConvertMszUtf8NToWCharAlloc(tReader, pcsc_cchReaderLen + 1, &size);

			if (tmp == nullptr)
			{
				status = ERROR_NOT_ENOUGH_MEMORY;
				goto out_fail;
			}

			free(tReader);

			PCSC_AddMemoryBlock(hContext, tmp);
			*(WCHAR**)mszReaderNames = tmp;
		}
		else
		{
			tReader[pcsc_cchReaderLen - 1] = '\0';
			PCSC_AddMemoryBlock(hContext, tReader);
			*(char**)mszReaderNames = tReader;
		}
	}

	pcsc_dwState &= 0xFFFF;

	if (pdwState)
		*pdwState = PCSC_ConvertCardStateToWinSCard((DWORD)pcsc_dwState, status);

	if (pdwProtocol)
		*pdwProtocol = PCSC_ConvertProtocolsToWinSCard((DWORD)pcsc_dwProtocol);

	if (pcbAtrLen)
		*pcbAtrLen = (DWORD)pcsc_cbAtrLen;

	if (pcchReaderLen)
	{
		WINPR_ASSERT(pcsc_cchReaderLen < UINT32_MAX);
		*pcchReaderLen = (DWORD)pcsc_cchReaderLen + 1u;
	}

	return (LONG)status;
out_fail:
	free(tReader);
	free(tATR);
	return (LONG)status;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardState(SCARDHANDLE hCard, LPDWORD pdwState,
                                                        LPDWORD pdwProtocol, LPBYTE pbAtr,
                                                        LPDWORD pcbAtrLen)
{
	DWORD cchReaderLen = 0;
	SCARDCONTEXT hContext = 0;
	LPSTR mszReaderNames = nullptr;
	PCSC_LONG status = SCARD_S_SUCCESS;
	PCSC_SCARDHANDLE* pCard = nullptr;
	DWORD pcsc_dwState = 0;
	DWORD pcsc_dwProtocol = 0;
	DWORD pcsc_cbAtrLen = 0;

	if (pcbAtrLen)
		pcsc_cbAtrLen = (DWORD)*pcbAtrLen;

	if (!g_PCSC.pfnSCardStatus)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardStatus");

	pCard = PCSC_GetCardHandleData(hCard);

	if (!pCard)
		return SCARD_E_INVALID_VALUE;

	PCSC_WaitForCardAccess(0, hCard, pCard->shared);
	hContext = PCSC_GetCardContextFromHandle(hCard);

	if (!hContext)
		return SCARD_E_INVALID_VALUE;

	cchReaderLen = SCARD_AUTOALLOCATE;
	status = PCSC_SCardStatus_Internal(hCard, (LPSTR)&mszReaderNames, &cchReaderLen, &pcsc_dwState,
	                                   &pcsc_dwProtocol, pbAtr, &pcsc_cbAtrLen, FALSE);

	if (mszReaderNames)
		PCSC_SCardFreeMemory_Internal(hContext, mszReaderNames);

	*pdwState = pcsc_dwState;
	*pdwProtocol = PCSC_ConvertProtocolsToWinSCard(pcsc_dwProtocol);
	if (pcbAtrLen)
		*pcbAtrLen = pcsc_cbAtrLen;
	return PCSC_MapErrorCodeToWinSCard(status);
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardStatusA(SCARDHANDLE hCard, LPSTR mszReaderNames,
                                                          LPDWORD pcchReaderLen, LPDWORD pdwState,
                                                          LPDWORD pdwProtocol, LPBYTE pbAtr,
                                                          LPDWORD pcbAtrLen)
{

	return PCSC_SCardStatus_Internal(hCard, mszReaderNames, pcchReaderLen, pdwState, pdwProtocol,
	                                 pbAtr, pcbAtrLen, FALSE);
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardStatusW(SCARDHANDLE hCard, LPWSTR mszReaderNames,
                                                          LPDWORD pcchReaderLen, LPDWORD pdwState,
                                                          LPDWORD pdwProtocol, LPBYTE pbAtr,
                                                          LPDWORD pcbAtrLen)
{

	return PCSC_SCardStatus_Internal(hCard, (LPSTR)mszReaderNames, pcchReaderLen, pdwState,
	                                 pdwProtocol, pbAtr, pcbAtrLen, TRUE);
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardTransmit(
    SCARDHANDLE hCard, LPCSCARD_IO_REQUEST pioSendPci, LPCBYTE pbSendBuffer, DWORD cbSendLength,
    LPSCARD_IO_REQUEST pioRecvPci, LPBYTE pbRecvBuffer, LPDWORD pcbRecvLength)
{
	PCSC_LONG status = SCARD_S_SUCCESS;
	PCSC_SCARDHANDLE* pCard = nullptr;
	PCSC_DWORD cbExtraBytes = 0;
	BYTE* pbExtraBytes = nullptr;
	BYTE* pcsc_pbExtraBytes = nullptr;
	PCSC_DWORD pcsc_cbSendLength = (PCSC_DWORD)cbSendLength;
	PCSC_DWORD pcsc_cbRecvLength = 0;
	union
	{
		const PCSC_SCARD_IO_REQUEST* pcs;
		PCSC_SCARD_IO_REQUEST* ps;
		LPSCARD_IO_REQUEST lps;
		LPCSCARD_IO_REQUEST lpcs;
		BYTE* pb;
	} sendPci, recvPci, inRecvPci, inSendPci;

	sendPci.ps = nullptr;
	recvPci.ps = nullptr;
	inRecvPci.lps = pioRecvPci;
	inSendPci.lpcs = pioSendPci;

	if (!g_PCSC.pfnSCardTransmit)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardTransmit");

	pCard = PCSC_GetCardHandleData(hCard);

	if (!pCard)
		return SCARD_E_INVALID_VALUE;

	PCSC_WaitForCardAccess(0, hCard, pCard->shared);

	if (!pcbRecvLength)
		return SCARD_E_INVALID_PARAMETER;

	if (*pcbRecvLength == SCARD_AUTOALLOCATE)
		return SCARD_E_INVALID_PARAMETER;

	pcsc_cbRecvLength = (PCSC_DWORD)*pcbRecvLength;

	if (!inSendPci.lpcs)
	{
		PCSC_DWORD dwState = 0;
		PCSC_DWORD cbAtrLen = 0;
		PCSC_DWORD dwProtocol = 0;
		PCSC_DWORD cchReaderLen = 0;
		/**
		 * pcsc-lite cannot have a null pioSendPci parameter, unlike WinSCard.
		 * Query the current protocol and use default SCARD_IO_REQUEST for it.
		 */
		status = g_PCSC.pfnSCardStatus(hCard, nullptr, &cchReaderLen, &dwState, &dwProtocol,
		                               nullptr, &cbAtrLen);

		if (status == SCARD_S_SUCCESS)
		{
			if (dwProtocol == SCARD_PROTOCOL_T0)
				sendPci.pcs = PCSC_SCARD_PCI_T0;
			else if (dwProtocol == SCARD_PROTOCOL_T1)
				sendPci.pcs = PCSC_SCARD_PCI_T1;
			else if (dwProtocol == PCSC_SCARD_PROTOCOL_RAW)
				sendPci.pcs = PCSC_SCARD_PCI_RAW;
		}
	}
	else
	{
		cbExtraBytes = inSendPci.lpcs->cbPciLength - sizeof(SCARD_IO_REQUEST);
		sendPci.ps = (PCSC_SCARD_IO_REQUEST*)malloc(sizeof(PCSC_SCARD_IO_REQUEST) + cbExtraBytes);

		if (!sendPci.ps)
			return SCARD_E_NO_MEMORY;

		sendPci.ps->dwProtocol = (PCSC_DWORD)inSendPci.lpcs->dwProtocol;
		sendPci.ps->cbPciLength = sizeof(PCSC_SCARD_IO_REQUEST) + cbExtraBytes;
		pbExtraBytes = &(inSendPci.pb)[sizeof(SCARD_IO_REQUEST)];
		pcsc_pbExtraBytes = &(sendPci.pb)[sizeof(PCSC_SCARD_IO_REQUEST)];
		CopyMemory(pcsc_pbExtraBytes, pbExtraBytes, cbExtraBytes);
	}

	if (inRecvPci.lps)
	{
		cbExtraBytes = inRecvPci.lps->cbPciLength - sizeof(SCARD_IO_REQUEST);
		recvPci.ps = (PCSC_SCARD_IO_REQUEST*)malloc(sizeof(PCSC_SCARD_IO_REQUEST) + cbExtraBytes);

		if (!recvPci.ps)
		{
			if (inSendPci.lpcs)
				free(sendPci.ps);

			return SCARD_E_NO_MEMORY;
		}

		recvPci.ps->dwProtocol = (PCSC_DWORD)inRecvPci.lps->dwProtocol;
		recvPci.ps->cbPciLength = sizeof(PCSC_SCARD_IO_REQUEST) + cbExtraBytes;
		pbExtraBytes = &(inRecvPci.pb)[sizeof(SCARD_IO_REQUEST)];
		pcsc_pbExtraBytes = &(recvPci.pb)[sizeof(PCSC_SCARD_IO_REQUEST)];
		CopyMemory(pcsc_pbExtraBytes, pbExtraBytes, cbExtraBytes);
	}

	status = g_PCSC.pfnSCardTransmit(hCard, sendPci.ps, pbSendBuffer, pcsc_cbSendLength, recvPci.ps,
	                                 pbRecvBuffer, &pcsc_cbRecvLength);

	*pcbRecvLength = (DWORD)pcsc_cbRecvLength;

	if (inSendPci.lpcs)
		free(sendPci.ps); /* pcsc_pioSendPci is dynamically allocated only when pioSendPci is
		                          non null */

	if (inRecvPci.lps)
	{
		cbExtraBytes = inRecvPci.lps->cbPciLength - sizeof(SCARD_IO_REQUEST);
		pbExtraBytes = &(inRecvPci.pb)[sizeof(SCARD_IO_REQUEST)];
		pcsc_pbExtraBytes = &(recvPci.pb)[sizeof(PCSC_SCARD_IO_REQUEST)];
		CopyMemory(pbExtraBytes, pcsc_pbExtraBytes, cbExtraBytes); /* copy extra bytes */
		free(recvPci.ps); /* pcsc_pioRecvPci is dynamically allocated only when pioRecvPci is
		                          non null */
	}

	return PCSC_MapErrorCodeToWinSCard(status);
}

WINPR_ATTR_NODISCARD static LONG WINAPI
PCSC_SCardGetTransmitCount(SCARDHANDLE hCard, WINPR_ATTR_UNUSED LPDWORD pcTransmitCount)
{
	PCSC_SCARDHANDLE* pCard = nullptr;

	pCard = PCSC_GetCardHandleData(hCard);

	if (!pCard)
		return SCARD_E_INVALID_VALUE;

	PCSC_WaitForCardAccess(0, hCard, pCard->shared);
	return SCARD_S_SUCCESS;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardControl(SCARDHANDLE hCard, DWORD dwControlCode,
                                                          LPCVOID lpInBuffer, DWORD cbInBufferSize,
                                                          LPVOID lpOutBuffer, DWORD cbOutBufferSize,
                                                          LPDWORD lpBytesReturned)
{
	DWORD IoCtlFunction = 0;
	DWORD IoCtlDeviceType = 0;
	BOOL getFeatureRequest = FALSE;
	PCSC_LONG status = SCARD_S_SUCCESS;
	PCSC_SCARDHANDLE* pCard = nullptr;
	PCSC_DWORD pcsc_dwControlCode = 0;
	PCSC_DWORD pcsc_cbInBufferSize = (PCSC_DWORD)cbInBufferSize;
	PCSC_DWORD pcsc_cbOutBufferSize = (PCSC_DWORD)cbOutBufferSize;
	PCSC_DWORD pcsc_BytesReturned = 0;

	if (!g_PCSC.pfnSCardControl)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardControl");

	pCard = PCSC_GetCardHandleData(hCard);

	if (!pCard)
		return SCARD_E_INVALID_VALUE;

	PCSC_WaitForCardAccess(0, hCard, pCard->shared);
	/**
	 * PCSCv2 Part 10:
	 * http://www.pcscworkgroup.com/specifications/files/pcsc10_v2.02.09.pdf
	 *
	 * Smart Card Driver IOCTLs:
	 * http://msdn.microsoft.com/en-us/library/windows/hardware/ff548988/
	 *
	 * Converting Windows Feature Request IOCTL code to the pcsc-lite control code:
	 * http://musclecard.996296.n3.nabble.com/Converting-Windows-Feature-Request-IOCTL-code-to-the-pcsc-lite-control-code-td4906.html
	 */
	IoCtlFunction = FUNCTION_FROM_CTL_CODE(dwControlCode);
	IoCtlDeviceType = DEVICE_TYPE_FROM_CTL_CODE(dwControlCode);

	if (dwControlCode == IOCTL_SMARTCARD_GET_FEATURE_REQUEST)
		getFeatureRequest = TRUE;

	if (IoCtlDeviceType == FILE_DEVICE_SMARTCARD)
		dwControlCode = PCSC_SCARD_CTL_CODE(IoCtlFunction);

	pcsc_dwControlCode = (PCSC_DWORD)dwControlCode;
	status = g_PCSC.pfnSCardControl(hCard, pcsc_dwControlCode, lpInBuffer, pcsc_cbInBufferSize,
	                                lpOutBuffer, pcsc_cbOutBufferSize, &pcsc_BytesReturned);

	*lpBytesReturned = (DWORD)pcsc_BytesReturned;

	if (getFeatureRequest)
	{
		UINT32 count = 0;
		PCSC_TLV_STRUCTURE* tlv = (PCSC_TLV_STRUCTURE*)lpOutBuffer;

		if ((*lpBytesReturned % sizeof(PCSC_TLV_STRUCTURE)) != 0)
			return SCARD_E_UNEXPECTED;

		count = *lpBytesReturned / sizeof(PCSC_TLV_STRUCTURE);

		for (DWORD index = 0; index < count; index++)
		{
			if (tlv[index].length != 4)
				return SCARD_E_UNEXPECTED;
		}
	}

	return PCSC_MapErrorCodeToWinSCard(status);
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardGetAttrib_Internal(SCARDHANDLE hCard,
                                                                     DWORD dwAttrId, LPBYTE pbAttr,
                                                                     LPDWORD pcbAttrLen)
{
	SCARDCONTEXT hContext = 0;
	BOOL pcbAttrLenAlloc = FALSE;
	PCSC_LONG status = SCARD_S_SUCCESS;
	PCSC_SCARDHANDLE* pCard = nullptr;
	PCSC_DWORD pcsc_dwAttrId = (PCSC_DWORD)dwAttrId;
	PCSC_DWORD pcsc_cbAttrLen = 0;

	if (!g_PCSC.pfnSCardGetAttrib)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardGetAttrib");

	pCard = PCSC_GetCardHandleData(hCard);

	if (!pCard)
		return SCARD_E_INVALID_VALUE;

	PCSC_WaitForCardAccess(0, hCard, pCard->shared);
	hContext = PCSC_GetCardContextFromHandle(hCard);

	if (!hContext)
		return SCARD_E_INVALID_HANDLE;

	if (!pcbAttrLen)
		return SCARD_E_INVALID_PARAMETER;

	if (*pcbAttrLen == SCARD_AUTOALLOCATE)
	{
		if (!pbAttr)
			return SCARD_E_INVALID_PARAMETER;
		pcbAttrLenAlloc = TRUE;
	}

	pcsc_cbAttrLen = pcbAttrLenAlloc ? PCSC_SCARD_AUTOALLOCATE : (PCSC_DWORD)*pcbAttrLen;

	if (pcbAttrLenAlloc && !g_SCardAutoAllocate)
	{
		pcsc_cbAttrLen = 0;
		status = g_PCSC.pfnSCardGetAttrib(hCard, pcsc_dwAttrId, nullptr, &pcsc_cbAttrLen);

		if (status == SCARD_S_SUCCESS)
		{
			BYTE* tmp = (BYTE*)calloc(1, pcsc_cbAttrLen);

			if (!tmp)
				return SCARD_E_NO_MEMORY;

			status = g_PCSC.pfnSCardGetAttrib(hCard, pcsc_dwAttrId, tmp, &pcsc_cbAttrLen);

			if (status != SCARD_S_SUCCESS)
			{
				free(tmp);
				tmp = nullptr;
			}
			else
				PCSC_AddMemoryBlock(hContext, tmp);
			*(BYTE**)pbAttr = tmp;
		}
	}
	else
	{
		status = g_PCSC.pfnSCardGetAttrib(hCard, pcsc_dwAttrId, pbAttr, &pcsc_cbAttrLen);
	}

	if (status == SCARD_S_SUCCESS)
		*pcbAttrLen = (DWORD)pcsc_cbAttrLen;
	return PCSC_MapErrorCodeToWinSCard(status);
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardGetAttrib_FriendlyName(SCARDHANDLE hCard,
                                                                         DWORD dwAttrId,
                                                                         LPBYTE pbAttr,
                                                                         LPDWORD pcbAttrLen)
{
	char* namePCSC = nullptr;
	char* pbAttrA = nullptr;

	SCARDCONTEXT hContext = PCSC_GetCardContextFromHandle(hCard);

	if (!hContext)
		return SCARD_E_INVALID_HANDLE;

	if (!pcbAttrLen)
		return SCARD_E_INVALID_PARAMETER;
	const DWORD cbAttrLen = *pcbAttrLen;
	*pcbAttrLen = SCARD_AUTOALLOCATE;
	LONG status = PCSC_SCardGetAttrib_Internal(hCard, SCARD_ATTR_DEVICE_FRIENDLY_NAME_A,
	                                           (LPBYTE)&pbAttrA, pcbAttrLen);

	if (status != SCARD_S_SUCCESS)
	{
		WCHAR* pbAttrW = nullptr;

		*pcbAttrLen = SCARD_AUTOALLOCATE;
		status = PCSC_SCardGetAttrib_Internal(hCard, SCARD_ATTR_DEVICE_FRIENDLY_NAME_W,
		                                      (LPBYTE)&pbAttrW, pcbAttrLen);

		if (status != SCARD_S_SUCCESS)
			return status;

		namePCSC = ConvertMszWCharNToUtf8Alloc(pbAttrW, *pcbAttrLen, nullptr);
		PCSC_SCardFreeMemory_Internal(hContext, pbAttrW);
	}
	else
	{
		namePCSC = strndup(pbAttrA, *pcbAttrLen);

		if (!namePCSC)
			return SCARD_E_NO_MEMORY;

		PCSC_SCardFreeMemory_Internal(hContext, pbAttrA);
	}

	size_t length = strnlen(namePCSC, *pcbAttrLen);

	if (dwAttrId == SCARD_ATTR_DEVICE_FRIENDLY_NAME_W)
	{
		size_t size = 0;
		WCHAR* friendlyNameW = ConvertUtf8NToWCharAlloc(namePCSC, length, &size);
		/* length here includes null terminator */

		if (!friendlyNameW)
			status = SCARD_E_NO_MEMORY;
		else
		{
			length = size + 1;

			if (cbAttrLen == SCARD_AUTOALLOCATE)
			{
				WINPR_ASSERT(length <= UINT32_MAX / sizeof(WCHAR));
				*(WCHAR**)pbAttr = friendlyNameW;
				*pcbAttrLen = (UINT32)length * sizeof(WCHAR);
				PCSC_AddMemoryBlock(hContext, friendlyNameW);
			}
			else
			{
				const size_t wlen = length * sizeof(WCHAR);
				*pcbAttrLen = WINPR_ASSERTING_INT_CAST(uint32_t, wlen);
				if ((wlen > cbAttrLen) && pbAttr)
					status = SCARD_E_INSUFFICIENT_BUFFER;
				else if (pbAttr)
					CopyMemory(pbAttr, friendlyNameW, (length * sizeof(WCHAR)));

				free(friendlyNameW);
			}
		}
		free(namePCSC);
	}
	else
	{
		length++; /* Include '\0' in length */
		if (cbAttrLen == SCARD_AUTOALLOCATE)
		{
			*(CHAR**)pbAttr = namePCSC;
			WINPR_ASSERT(length <= UINT32_MAX);
			*pcbAttrLen = (UINT32)length;
			PCSC_AddMemoryBlock(hContext, namePCSC);
		}
		else
		{
			*pcbAttrLen = WINPR_ASSERTING_INT_CAST(uint32_t, length);
			if ((length > cbAttrLen) && pbAttr)
				status = SCARD_E_INSUFFICIENT_BUFFER;
			else if (pbAttr)
				CopyMemory(pbAttr, namePCSC, length);

			free(namePCSC);
		}
	}

	return status;
}

static LONG PCSC_ReadDeviceSystemNameGet(WINPR_ATTR_UNUSED SCARDCONTEXT hContext, SCARDHANDLE hCard,
                                         DWORD dwAttrId, LPBYTE* pbAttr, LPDWORD pcbAttrLen)
{
	PCSC_DWORD cchReader = 0;
	PCSC_DWORD cbAtr = 0;
	PCSC_DWORD dwState = 0;
	PCSC_DWORD dwProtocol = 0;

	const PCSC_LONG rc =
	    g_PCSC.pfnSCardStatus(hCard, nullptr, &cchReader, &dwState, &dwProtocol, nullptr, &cbAtr);
	if (rc != SCARD_S_SUCCESS)
		return (LONG)rc;

	void* tmp = calloc(cchReader + 1, sizeof(CHAR));
	if (!tmp)
		return SCARD_E_NO_MEMORY;
	const PCSC_LONG rc2 =
	    g_PCSC.pfnSCardStatus(hCard, tmp, &cchReader, &dwState, &dwProtocol, nullptr, &cbAtr);
	if (rc2 != SCARD_S_SUCCESS)
	{
		free(tmp);
		return (LONG)rc2;
	}

	if (dwAttrId == SCARD_ATTR_DEVICE_SYSTEM_NAME_W)
	{
		size_t len = 0;
		void* data = ConvertMszUtf8NToWCharAlloc(tmp, cchReader, &len);
		if (!data)
		{
			free(tmp);
			return SCARD_E_NO_MEMORY;
		}
		len *= sizeof(WCHAR);

		cchReader = WINPR_ASSERTING_INT_CAST(PCSC_DWORD, len);
		free(tmp);
		tmp = data;
	}

	*pbAttr = tmp;
	*pcbAttrLen = WINPR_ASSERTING_INT_CAST(DWORD, cchReader);
	return SCARD_S_SUCCESS;
}

static LONG PCSC_ReadDeviceSystemName(WINPR_ATTR_UNUSED SCARDCONTEXT hContext, SCARDHANDLE hCard,
                                      DWORD dwAttrId, LPBYTE pbAttr, LPDWORD pcbAttrLen)
{
	BYTE* tmp = nullptr;
	DWORD cbAttrLen = 0;
	const LONG rc = PCSC_ReadDeviceSystemNameGet(hContext, hCard, dwAttrId, &tmp, &cbAttrLen);
	if (rc != SCARD_S_SUCCESS)
		return rc;

	if (*pcbAttrLen == SCARD_AUTOALLOCATE)
	{
		if (!PCSC_AddMemoryBlock(hContext, tmp))
		{
			free(tmp);
			return SCARD_E_NO_MEMORY;
		}

		*pcbAttrLen = cbAttrLen;
		*(BYTE**)pbAttr = tmp;
		return SCARD_S_SUCCESS;
	}

	if (pbAttr)
		memcpy(pbAttr, tmp, MIN(cbAttrLen, *pcbAttrLen));
	free(tmp);

	if (pbAttr)
	{
		if (cbAttrLen > *pcbAttrLen)
			return SCARD_E_INSUFFICIENT_BUFFER;
	}

	*pcbAttrLen = cbAttrLen;

	return SCARD_S_SUCCESS;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardGetAttrib(SCARDHANDLE hCard, DWORD dwAttrId,
                                                            LPBYTE pbAttr, LPDWORD pcbAttrLen)
{
	DWORD cbAttrLen = 0;
	SCARDCONTEXT hContext = 0;
	BOOL pcbAttrLenAlloc = FALSE;
	PCSC_LONG status = SCARD_S_SUCCESS;

	if (nullptr == pcbAttrLen)
		return SCARD_E_INVALID_PARAMETER;

	cbAttrLen = *pcbAttrLen;

	if (*pcbAttrLen == SCARD_AUTOALLOCATE)
	{
		if (nullptr == pbAttr)
			return SCARD_E_INVALID_PARAMETER;

		pcbAttrLenAlloc = TRUE;
		*(BYTE**)pbAttr = nullptr;
	}
	else
	{
		/**
		 * pcsc-lite returns SCARD_E_INSUFFICIENT_BUFFER if the given
		 * buffer size is larger than PCSC_MAX_BUFFER_SIZE (264)
		 */
		if (*pcbAttrLen > PCSC_MAX_BUFFER_SIZE)
			*pcbAttrLen = PCSC_MAX_BUFFER_SIZE;
	}

	hContext = PCSC_GetCardContextFromHandle(hCard);

	if (!hContext)
		return SCARD_E_INVALID_HANDLE;

	if ((dwAttrId == SCARD_ATTR_DEVICE_FRIENDLY_NAME_A) ||
	    (dwAttrId == SCARD_ATTR_DEVICE_FRIENDLY_NAME_W))
	{
		return PCSC_SCardGetAttrib_FriendlyName(hCard, dwAttrId, pbAttr, pcbAttrLen);
	}

	status = PCSC_SCardGetAttrib_Internal(hCard, dwAttrId, pbAttr, pcbAttrLen);

	if (status == SCARD_S_SUCCESS)
	{
		if (dwAttrId == SCARD_ATTR_VENDOR_NAME)
		{
			if (pbAttr)
			{
				const char* vendorName = nullptr;

				/**
				 * pcsc-lite adds a null terminator to the vendor name,
				 * while WinSCard doesn't. Strip the null terminator.
				 */

				if (pcbAttrLenAlloc)
					vendorName = (char*)*(BYTE**)pbAttr;
				else
					vendorName = (char*)pbAttr;

				if (vendorName)
				{
					size_t len = strnlen(vendorName, *pcbAttrLen);
					WINPR_ASSERT(len < UINT32_MAX);
					*pcbAttrLen = (DWORD)len + 1;
				}
				else
					*pcbAttrLen = 0;
			}
		}
	}
	else
	{
		if (dwAttrId == SCARD_ATTR_CURRENT_PROTOCOL_TYPE)
		{
			if (!pcbAttrLenAlloc)
			{
				PCSC_DWORD dwState = 0;
				PCSC_DWORD cbAtrLen = 0;
				PCSC_DWORD dwProtocol = 0;
				PCSC_DWORD cchReaderLen = 0;
				status = g_PCSC.pfnSCardStatus(hCard, nullptr, &cchReaderLen, &dwState, &dwProtocol,
				                               nullptr, &cbAtrLen);

				if (status == SCARD_S_SUCCESS)
				{
					if ((cbAttrLen < sizeof(DWORD)) && pbAttr)
						return SCARD_E_INSUFFICIENT_BUFFER;

					if (pbAttr)
						*(DWORD*)pbAttr = PCSC_ConvertProtocolsToWinSCard(dwProtocol);
					*pcbAttrLen = sizeof(DWORD);
				}
			}
		}
		else if (dwAttrId == SCARD_ATTR_CHANNEL_ID)
		{
			if (!pcbAttrLenAlloc)
			{
				UINT32 channelType = 0x20; /* USB */
				UINT32 channelNumber = 0;

				if ((cbAttrLen < sizeof(DWORD)) && pbAttr)
					return SCARD_E_INSUFFICIENT_BUFFER;

				status = SCARD_S_SUCCESS;
				if (pbAttr)
					*(DWORD*)pbAttr = (channelType << 16u) | channelNumber;
				*pcbAttrLen = sizeof(DWORD);
			}
		}
		else if (dwAttrId == SCARD_ATTR_VENDOR_IFD_TYPE)
		{
		}
		else if (dwAttrId == SCARD_ATTR_DEFAULT_CLK)
		{
		}
		else if (dwAttrId == SCARD_ATTR_DEFAULT_DATA_RATE)
		{
		}
		else if (dwAttrId == SCARD_ATTR_MAX_CLK)
		{
		}
		else if (dwAttrId == SCARD_ATTR_MAX_DATA_RATE)
		{
		}
		else if (dwAttrId == SCARD_ATTR_MAX_IFSD)
		{
		}
		else if (dwAttrId == SCARD_ATTR_CHARACTERISTICS)
		{
		}
		else if (dwAttrId == SCARD_ATTR_DEVICE_SYSTEM_NAME_A)
		{
			status = PCSC_ReadDeviceSystemName(hContext, hCard, dwAttrId, pbAttr, pcbAttrLen);
		}
		else if (dwAttrId == SCARD_ATTR_DEVICE_SYSTEM_NAME_W)
		{
			status = PCSC_ReadDeviceSystemName(hContext, hCard, dwAttrId, pbAttr, pcbAttrLen);
		}
		else if (dwAttrId == SCARD_ATTR_DEVICE_UNIT)
		{
		}
		else if (dwAttrId == SCARD_ATTR_POWER_MGMT_SUPPORT)
		{
		}
		else if (dwAttrId == SCARD_ATTR_CURRENT_CLK)
		{
		}
		else if (dwAttrId == SCARD_ATTR_CURRENT_F)
		{
		}
		else if (dwAttrId == SCARD_ATTR_CURRENT_D)
		{
		}
		else if (dwAttrId == SCARD_ATTR_CURRENT_N)
		{
		}
		else if (dwAttrId == SCARD_ATTR_CURRENT_CWT)
		{
		}
		else if (dwAttrId == SCARD_ATTR_CURRENT_BWT)
		{
		}
		else if (dwAttrId == SCARD_ATTR_CURRENT_IFSC)
		{
		}
		else if (dwAttrId == SCARD_ATTR_CURRENT_EBC_ENCODING)
		{
		}
		else if (dwAttrId == SCARD_ATTR_CURRENT_IFSD)
		{
		}
		else if (dwAttrId == SCARD_ATTR_ICC_TYPE_PER_ATR)
		{
		}
	}

	return WINPR_ASSERTING_INT_CAST(LONG, status);
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardSetAttrib(SCARDHANDLE hCard, DWORD dwAttrId,
                                                            LPCBYTE pbAttr, DWORD cbAttrLen)
{
	PCSC_LONG status = SCARD_S_SUCCESS;
	PCSC_SCARDHANDLE* pCard = nullptr;
	PCSC_DWORD pcsc_dwAttrId = (PCSC_DWORD)dwAttrId;
	PCSC_DWORD pcsc_cbAttrLen = (PCSC_DWORD)cbAttrLen;

	if (!g_PCSC.pfnSCardSetAttrib)
		return PCSC_SCard_LogError("g_PCSC.pfnSCardSetAttrib");

	pCard = PCSC_GetCardHandleData(hCard);

	if (!pCard)
		return SCARD_E_INVALID_VALUE;

	PCSC_WaitForCardAccess(0, hCard, pCard->shared);
	status = g_PCSC.pfnSCardSetAttrib(hCard, pcsc_dwAttrId, pbAttr, pcsc_cbAttrLen);
	return PCSC_MapErrorCodeToWinSCard(status);
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardUIDlgSelectCardA(LPOPENCARDNAMEA_EX pDlgStruc)
{
	WINPR_UNUSED(pDlgStruc);

	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardUIDlgSelectCardW(LPOPENCARDNAMEW_EX pDlgStruc)
{
	WINPR_UNUSED(pDlgStruc);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_GetOpenCardNameA(LPOPENCARDNAMEA pDlgStruc)
{
	WINPR_UNUSED(pDlgStruc);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_GetOpenCardNameW(LPOPENCARDNAMEW pDlgStruc)
{
	WINPR_UNUSED(pDlgStruc);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardDlgExtendedError(void)
{

	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD
static char* card_id_and_name_a(const UUID* CardIdentifier, LPCSTR LookupName)
{
	if (!CardIdentifier || !LookupName)
		return nullptr;

	size_t len = strlen(LookupName) + 34;
	char* id = malloc(len);
	if (!id)
		return nullptr;

	(void)snprintf(id, len, "%08X%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X\\%s",
	               CardIdentifier->Data1, CardIdentifier->Data2, CardIdentifier->Data3,
	               CardIdentifier->Data4[0], CardIdentifier->Data4[1], CardIdentifier->Data4[2],
	               CardIdentifier->Data4[3], CardIdentifier->Data4[4], CardIdentifier->Data4[5],
	               CardIdentifier->Data4[6], CardIdentifier->Data4[7], LookupName);
	return id;
}

WINPR_ATTR_NODISCARD
static char* card_id_and_name_w(const UUID* CardIdentifier, LPCWSTR LookupName)
{
	char* res = nullptr;
	char* tmp = ConvertWCharToUtf8Alloc(LookupName, nullptr);
	if (!tmp)
		return nullptr;
	res = card_id_and_name_a(CardIdentifier, tmp);
	free(tmp);
	return res;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardReadCacheA(SCARDCONTEXT hContext,
                                                             UUID* CardIdentifier,
                                                             DWORD FreshnessCounter,
                                                             LPSTR LookupName, PBYTE Data,
                                                             DWORD* DataLen)
{
	PCSC_CACHE_ITEM* data = nullptr;
	PCSC_SCARDCONTEXT* ctx = PCSC_GetCardContextData(hContext);
	if (!ctx)
		return SCARD_E_INVALID_HANDLE;

	char* id = card_id_and_name_a(CardIdentifier, LookupName);
	if (id)
		data = HashTable_GetItemValue(ctx->cache, id);
	free(id);

	if (!data)
	{
		*DataLen = 0;
		return SCARD_W_CACHE_ITEM_NOT_FOUND;
	}

	if (FreshnessCounter != data->freshness)
	{
		*DataLen = 0;
		return SCARD_W_CACHE_ITEM_STALE;
	}

	if (*DataLen == SCARD_AUTOALLOCATE)
	{
		BYTE* mem = calloc(1, data->len);
		if (!mem)
			return SCARD_E_NO_MEMORY;

		if (!PCSC_AddMemoryBlock(hContext, mem))
		{
			free(mem);
			return SCARD_E_NO_MEMORY;
		}

		memcpy(mem, data->data, data->len);
		*(BYTE**)Data = mem;
	}
	else
	{
		if (data->len > *DataLen)
			return SCARD_E_INSUFFICIENT_BUFFER;
		memcpy(Data, data->data, data->len);
	}

	*DataLen = data->len;
	return SCARD_S_SUCCESS;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardReadCacheW(SCARDCONTEXT hContext,
                                                             UUID* CardIdentifier,
                                                             DWORD FreshnessCounter,
                                                             LPWSTR LookupName, PBYTE Data,
                                                             DWORD* DataLen)
{
	PCSC_CACHE_ITEM* data = nullptr;
	PCSC_SCARDCONTEXT* ctx = PCSC_GetCardContextData(hContext);
	if (!ctx)
		return SCARD_E_INVALID_HANDLE;

	char* id = card_id_and_name_w(CardIdentifier, LookupName);

	data = HashTable_GetItemValue(ctx->cache, id);
	free(id);

	if (!data)
	{
		*DataLen = 0;
		return SCARD_W_CACHE_ITEM_NOT_FOUND;
	}

	if (FreshnessCounter != data->freshness)
	{
		*DataLen = 0;
		return SCARD_W_CACHE_ITEM_STALE;
	}

	if (*DataLen == SCARD_AUTOALLOCATE)
	{
		BYTE* mem = calloc(1, data->len);
		if (!mem)
			return SCARD_E_NO_MEMORY;

		if (!PCSC_AddMemoryBlock(hContext, mem))
		{
			free(mem);
			return SCARD_E_NO_MEMORY;
		}

		memcpy(mem, data->data, data->len);
		*(BYTE**)Data = mem;
	}
	else
	{
		if (data->len > *DataLen)
			return SCARD_E_INSUFFICIENT_BUFFER;
		memcpy(Data, data->data, data->len);
	}
	*DataLen = data->len;
	return SCARD_S_SUCCESS;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardWriteCacheA(SCARDCONTEXT hContext,
                                                              UUID* CardIdentifier,
                                                              DWORD FreshnessCounter,
                                                              LPSTR LookupName, PBYTE Data,
                                                              DWORD DataLen)
{
	PCSC_CACHE_ITEM* data = nullptr;
	PCSC_SCARDCONTEXT* ctx = PCSC_GetCardContextData(hContext);
	char* id = nullptr;

	if (!ctx)
		return SCARD_E_FILE_NOT_FOUND;

	id = card_id_and_name_a(CardIdentifier, LookupName);

	if (!id)
		return SCARD_E_NO_MEMORY;

	data = malloc(sizeof(PCSC_CACHE_ITEM));
	if (!data)
	{
		free(id);
		return SCARD_E_NO_MEMORY;
	}
	data->data = calloc(DataLen, 1);
	if (!data->data)
	{
		free(id);
		free(data);
		return SCARD_E_NO_MEMORY;
	}
	data->len = DataLen;
	data->freshness = FreshnessCounter;
	memcpy(data->data, Data, data->len);

	HashTable_Remove(ctx->cache, id);
	const BOOL rc = HashTable_Insert(ctx->cache, id, data);
	free(id);

	if (!rc)
	{
		pcsc_cache_item_free(data);
		return SCARD_E_NO_MEMORY;
	}

	// NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert owns data
	return SCARD_S_SUCCESS;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardWriteCacheW(SCARDCONTEXT hContext,
                                                              UUID* CardIdentifier,
                                                              DWORD FreshnessCounter,
                                                              LPWSTR LookupName, PBYTE Data,
                                                              DWORD DataLen)
{
	PCSC_CACHE_ITEM* data = nullptr;
	PCSC_SCARDCONTEXT* ctx = PCSC_GetCardContextData(hContext);
	char* id = nullptr;
	if (!ctx)
		return SCARD_E_FILE_NOT_FOUND;

	id = card_id_and_name_w(CardIdentifier, LookupName);

	if (!id)
		return SCARD_E_NO_MEMORY;

	data = malloc(sizeof(PCSC_CACHE_ITEM));
	if (!data)
	{
		free(id);
		return SCARD_E_NO_MEMORY;
	}
	data->data = malloc(DataLen);
	if (!data->data)
	{
		free(id);
		free(data);
		return SCARD_E_NO_MEMORY;
	}
	data->len = DataLen;
	data->freshness = FreshnessCounter;
	memcpy(data->data, Data, data->len);

	HashTable_Remove(ctx->cache, id);
	const BOOL rc = HashTable_Insert(ctx->cache, id, data);
	free(id);

	if (!rc)
	{
		pcsc_cache_item_free(data);
		return SCARD_E_NO_MEMORY;
	}

	// NOLINTNEXTLINE(clang-analyzer-unix.Malloc): HashTable_Insert owns data
	return SCARD_S_SUCCESS;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardGetReaderIconA(
    SCARDCONTEXT hContext, LPCSTR szReaderName,
    LPBYTE pbIcon /* NOLINT(readability-non-const-parameter) */, LPDWORD pcbIcon)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szReaderName);
	WINPR_UNUSED(pbIcon);
	WINPR_ASSERT(pcbIcon);
	*pcbIcon = 0;
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardGetReaderIconW(
    SCARDCONTEXT hContext, LPCWSTR szReaderName,
    LPBYTE pbIcon /* NOLINT(readability-non-const-parameter) */, LPDWORD pcbIcon)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szReaderName);
	WINPR_UNUSED(pbIcon);
	WINPR_ASSERT(pcbIcon);
	*pcbIcon = 0;
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD static LONG WINAPI PCSC_SCardGetDeviceTypeIdA(SCARDCONTEXT hContext,
                                                                   LPCSTR szReaderName,
                                                                   LPDWORD pdwDeviceTypeId)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szReaderName);
	WINPR_UNUSED(pdwDeviceTypeId);
	if (pdwDeviceTypeId)
		*pdwDeviceTypeId = SCARD_READER_TYPE_USB;
	return SCARD_S_SUCCESS;
}

WINPR_ATTR_NODISCARD
static LONG WINAPI PCSC_SCardGetDeviceTypeIdW(SCARDCONTEXT hContext, LPCWSTR szReaderName,
                                              LPDWORD pdwDeviceTypeId)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szReaderName);
	if (pdwDeviceTypeId)
		*pdwDeviceTypeId = SCARD_READER_TYPE_USB;
	return SCARD_S_SUCCESS;
}

WINPR_ATTR_NODISCARD
static LONG WINAPI PCSC_SCardGetReaderDeviceInstanceIdA(
    SCARDCONTEXT hContext, LPCSTR szReaderName,
    LPSTR szDeviceInstanceId /* NOLINT(readability-non-const-parameter) */,
    LPDWORD pcchDeviceInstanceId /* NOLINT(readability-non-const-parameter) */)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szReaderName);
	WINPR_UNUSED(szDeviceInstanceId);
	WINPR_UNUSED(pcchDeviceInstanceId);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD
static LONG WINAPI PCSC_SCardGetReaderDeviceInstanceIdW(
    SCARDCONTEXT hContext, LPCWSTR szReaderName,
    LPWSTR szDeviceInstanceId /* NOLINT(readability-non-const-parameter) */,
    LPDWORD pcchDeviceInstanceId /* NOLINT(readability-non-const-parameter) */)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szReaderName);
	WINPR_UNUSED(szDeviceInstanceId);
	WINPR_UNUSED(pcchDeviceInstanceId);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD
static LONG WINAPI PCSC_SCardListReadersWithDeviceInstanceIdA(
    SCARDCONTEXT hContext, LPCSTR szDeviceInstanceId,
    LPSTR mszReaders /* NOLINT(readability-non-const-parameter) */,
    LPDWORD pcchReaders /* NOLINT(readability-non-const-parameter) */)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szDeviceInstanceId);
	WINPR_UNUSED(mszReaders);
	WINPR_UNUSED(pcchReaders);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD
static LONG WINAPI PCSC_SCardListReadersWithDeviceInstanceIdW(
    SCARDCONTEXT hContext, LPCWSTR szDeviceInstanceId,
    LPWSTR mszReaders /* NOLINT(readability-non-const-parameter) */,
    LPDWORD pcchReaders /* NOLINT(readability-non-const-parameter) */)
{
	WINPR_UNUSED(hContext);
	WINPR_UNUSED(szDeviceInstanceId);
	WINPR_UNUSED(mszReaders);
	WINPR_UNUSED(pcchReaders);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

WINPR_ATTR_NODISCARD
static LONG WINAPI PCSC_SCardAudit(SCARDCONTEXT hContext, DWORD dwEvent)
{

	WINPR_UNUSED(hContext);
	WINPR_UNUSED(dwEvent);
	return SCARD_E_UNSUPPORTED_FEATURE;
}

#ifdef __MACOSX__
WINPR_ATTR_NODISCARD
unsigned int determineMacOSXVersion(void)
{
	int mib[2];
	size_t len = 0;
	char* kernelVersion = nullptr;
	char* tok = nullptr;
	unsigned int version = 0;
	long majorVersion = 0;
	long minorVersion = 0;
	long patchVersion = 0;
	int count = 0;
	char* context = nullptr;
	mib[0] = CTL_KERN;
	mib[1] = KERN_OSRELEASE;

	if (sysctl(mib, 2, nullptr, &len, nullptr, 0) != 0)
		return 0;

	kernelVersion = calloc(len, sizeof(char));

	if (!kernelVersion)
		return 0;

	if (sysctl(mib, 2, kernelVersion, &len, nullptr, 0) != 0)
	{
		free(kernelVersion);
		return 0;
	}

	tok = strtok_s(kernelVersion, ".", &context);
	errno = 0;

	while (tok)
	{
		switch (count)
		{
			case 0:
				majorVersion = strtol(tok, nullptr, 0);

				if (errno != 0)
					goto fail;

				break;

			case 1:
				minorVersion = strtol(tok, nullptr, 0);

				if (errno != 0)
					goto fail;

				break;

			case 2:
				patchVersion = strtol(tok, nullptr, 0);

				if (errno != 0)
					goto fail;

				break;
		}

		tok = strtok_s(nullptr, ".", &context);
		count++;
	}

	/**
	 * Source : http://en.wikipedia.org/wiki/Darwin_(operating_system)
	 **/
	if (majorVersion < 5)
	{
		if (minorVersion < 4)
			version = 0x10000000;
		else
			version = 0x10010000;
	}
	else
	{
		switch (majorVersion)
		{
			case 5:
				version = 0x10010000;
				break;

			case 6:
				version = 0x10020000;
				break;

			case 7:
				version = 0x10030000;
				break;

			case 8:
				version = 0x10040000;
				break;

			case 9:
				version = 0x10050000;
				break;

			case 10:
				version = 0x10060000;
				break;

			case 11:
				version = 0x10070000;
				break;

			case 12:
				version = 0x10080000;
				break;

			case 13:
				version = 0x10090000;
				break;

			default:
				version = 0x10100000;
				break;
		}

		version |= (minorVersion << 8) | (patchVersion);
	}

fail:
	free(kernelVersion);
	return version;
}
#endif

static const SCardApiFunctionTable PCSC_SCardApiFunctionTable = {
	0, /* dwVersion */
	0, /* dwFlags */

	PCSC_SCardEstablishContext,                 /* SCardEstablishContext */
	PCSC_SCardReleaseContext,                   /* SCardReleaseContext */
	PCSC_SCardIsValidContext,                   /* SCardIsValidContext */
	PCSC_SCardListReaderGroupsA,                /* SCardListReaderGroupsA */
	PCSC_SCardListReaderGroupsW,                /* SCardListReaderGroupsW */
	PCSC_SCardListReadersA,                     /* SCardListReadersA */
	PCSC_SCardListReadersW,                     /* SCardListReadersW */
	PCSC_SCardListCardsA,                       /* SCardListCardsA */
	PCSC_SCardListCardsW,                       /* SCardListCardsW */
	PCSC_SCardListInterfacesA,                  /* SCardListInterfacesA */
	PCSC_SCardListInterfacesW,                  /* SCardListInterfacesW */
	PCSC_SCardGetProviderIdA,                   /* SCardGetProviderIdA */
	PCSC_SCardGetProviderIdW,                   /* SCardGetProviderIdW */
	PCSC_SCardGetCardTypeProviderNameA,         /* SCardGetCardTypeProviderNameA */
	PCSC_SCardGetCardTypeProviderNameW,         /* SCardGetCardTypeProviderNameW */
	PCSC_SCardIntroduceReaderGroupA,            /* SCardIntroduceReaderGroupA */
	PCSC_SCardIntroduceReaderGroupW,            /* SCardIntroduceReaderGroupW */
	PCSC_SCardForgetReaderGroupA,               /* SCardForgetReaderGroupA */
	PCSC_SCardForgetReaderGroupW,               /* SCardForgetReaderGroupW */
	PCSC_SCardIntroduceReaderA,                 /* SCardIntroduceReaderA */
	PCSC_SCardIntroduceReaderW,                 /* SCardIntroduceReaderW */
	PCSC_SCardForgetReaderA,                    /* SCardForgetReaderA */
	PCSC_SCardForgetReaderW,                    /* SCardForgetReaderW */
	PCSC_SCardAddReaderToGroupA,                /* SCardAddReaderToGroupA */
	PCSC_SCardAddReaderToGroupW,                /* SCardAddReaderToGroupW */
	PCSC_SCardRemoveReaderFromGroupA,           /* SCardRemoveReaderFromGroupA */
	PCSC_SCardRemoveReaderFromGroupW,           /* SCardRemoveReaderFromGroupW */
	PCSC_SCardIntroduceCardTypeA,               /* SCardIntroduceCardTypeA */
	PCSC_SCardIntroduceCardTypeW,               /* SCardIntroduceCardTypeW */
	PCSC_SCardSetCardTypeProviderNameA,         /* SCardSetCardTypeProviderNameA */
	PCSC_SCardSetCardTypeProviderNameW,         /* SCardSetCardTypeProviderNameW */
	PCSC_SCardForgetCardTypeA,                  /* SCardForgetCardTypeA */
	PCSC_SCardForgetCardTypeW,                  /* SCardForgetCardTypeW */
	PCSC_SCardFreeMemory,                       /* SCardFreeMemory */
	PCSC_SCardAccessStartedEvent,               /* SCardAccessStartedEvent */
	PCSC_SCardReleaseStartedEvent,              /* SCardReleaseStartedEvent */
	PCSC_SCardLocateCardsA,                     /* SCardLocateCardsA */
	PCSC_SCardLocateCardsW,                     /* SCardLocateCardsW */
	PCSC_SCardLocateCardsByATRA,                /* SCardLocateCardsByATRA */
	PCSC_SCardLocateCardsByATRW,                /* SCardLocateCardsByATRW */
	PCSC_SCardGetStatusChangeA,                 /* SCardGetStatusChangeA */
	PCSC_SCardGetStatusChangeW,                 /* SCardGetStatusChangeW */
	PCSC_SCardCancel,                           /* SCardCancel */
	PCSC_SCardConnectA,                         /* SCardConnectA */
	PCSC_SCardConnectW,                         /* SCardConnectW */
	PCSC_SCardReconnect,                        /* SCardReconnect */
	PCSC_SCardDisconnect,                       /* SCardDisconnect */
	PCSC_SCardBeginTransaction,                 /* SCardBeginTransaction */
	PCSC_SCardEndTransaction,                   /* SCardEndTransaction */
	PCSC_SCardCancelTransaction,                /* SCardCancelTransaction */
	PCSC_SCardState,                            /* SCardState */
	PCSC_SCardStatusA,                          /* SCardStatusA */
	PCSC_SCardStatusW,                          /* SCardStatusW */
	PCSC_SCardTransmit,                         /* SCardTransmit */
	PCSC_SCardGetTransmitCount,                 /* SCardGetTransmitCount */
	PCSC_SCardControl,                          /* SCardControl */
	PCSC_SCardGetAttrib,                        /* SCardGetAttrib */
	PCSC_SCardSetAttrib,                        /* SCardSetAttrib */
	PCSC_SCardUIDlgSelectCardA,                 /* SCardUIDlgSelectCardA */
	PCSC_SCardUIDlgSelectCardW,                 /* SCardUIDlgSelectCardW */
	PCSC_GetOpenCardNameA,                      /* GetOpenCardNameA */
	PCSC_GetOpenCardNameW,                      /* GetOpenCardNameW */
	PCSC_SCardDlgExtendedError,                 /* SCardDlgExtendedError */
	PCSC_SCardReadCacheA,                       /* SCardReadCacheA */
	PCSC_SCardReadCacheW,                       /* SCardReadCacheW */
	PCSC_SCardWriteCacheA,                      /* SCardWriteCacheA */
	PCSC_SCardWriteCacheW,                      /* SCardWriteCacheW */
	PCSC_SCardGetReaderIconA,                   /* SCardGetReaderIconA */
	PCSC_SCardGetReaderIconW,                   /* SCardGetReaderIconW */
	PCSC_SCardGetDeviceTypeIdA,                 /* SCardGetDeviceTypeIdA */
	PCSC_SCardGetDeviceTypeIdW,                 /* SCardGetDeviceTypeIdW */
	PCSC_SCardGetReaderDeviceInstanceIdA,       /* SCardGetReaderDeviceInstanceIdA */
	PCSC_SCardGetReaderDeviceInstanceIdW,       /* SCardGetReaderDeviceInstanceIdW */
	PCSC_SCardListReadersWithDeviceInstanceIdA, /* SCardListReadersWithDeviceInstanceIdA */
	PCSC_SCardListReadersWithDeviceInstanceIdW, /* SCardListReadersWithDeviceInstanceIdW */
	PCSC_SCardAudit                             /* SCardAudit */
};

const SCardApiFunctionTable* PCSC_GetSCardApiFunctionTable(void)
{
	return &PCSC_SCardApiFunctionTable;
}

int PCSC_InitializeSCardApi(void)
{
	/* Disable pcsc-lite's (poor) blocking so we can handle it ourselves */
	SetEnvironmentVariableA("PCSCLITE_NO_BLOCKING", "1");
#ifdef __MACOSX__
	g_PCSCModule = LoadLibraryX("/System/Library/Frameworks/PCSC.framework/PCSC");
	OSXVersion = determineMacOSXVersion();

	if (OSXVersion == 0)
		return -1;

#else
	g_PCSCModule = LoadLibraryA("libpcsclite.so.1");

	if (!g_PCSCModule)
		g_PCSCModule = LoadLibraryA("libpcsclite.so");

#endif

	if (!g_PCSCModule)
		return -1;

		/* symbols defined in winpr/smartcard.h, might pose an issue with the GetProcAddress macro
		 * below. therefore undefine them here */
#undef SCardListReaderGroups
#undef SCardListReaders
#undef SCardListCards
#undef SCardListInterfaces
#undef SCardGetProviderId
#undef SCardGetCardTypeProviderName
#undef SCardIntroduceReaderGroup
#undef SCardForgetReaderGroup
#undef SCardIntroduceReader
#undef SCardForgetReader
#undef SCardAddReaderToGroup
#undef SCardRemoveReaderFromGroup
#undef SCardIntroduceCardType
#undef SCardSetCardTypeProviderName
#undef SCardForgetCardType
#undef SCardLocateCards
#undef SCardLocateCardsByATR
#undef SCardGetStatusChange
#undef SCardConnect
#undef SCardStatus
#undef SCardUIDlgSelectCard
#undef GetOpenCardName
#undef SCardReadCache
#undef SCardWriteCache
#undef SCardGetReaderIcon
#undef SCardGetDeviceTypeId
#undef SCardGetReaderDeviceInstanceId
#undef SCardListReadersWithDeviceInstanceId

	WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardEstablishContext);
	WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardReleaseContext);
	WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardIsValidContext);
	WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardConnect);
	WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardReconnect);
	WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardDisconnect);
	WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardBeginTransaction);
	WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardEndTransaction);
	WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardStatus);
	WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardGetStatusChange);

#ifdef __MACOSX__

	if (OSXVersion >= 0x10050600)
	{
		WINSCARD_LOAD_PROC_EX(g_PCSCModule, g_PCSC, SCardControl, SCardControl132);
	}
	else
	{
		WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardControl);
	}
#else
	WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardControl);
#endif
	WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardTransmit);
	WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardListReaderGroups);
	WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardListReaders);
	WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardCancel);
	WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardGetAttrib);
	WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardSetAttrib);
	g_PCSC.pfnSCardFreeMemory = nullptr;
#ifndef __APPLE__
	WINSCARD_LOAD_PROC(g_PCSCModule, g_PCSC, SCardFreeMemory);
#endif

	if (g_PCSC.pfnSCardFreeMemory)
		g_SCardAutoAllocate = TRUE;

#ifdef DISABLE_PCSC_SCARD_AUTOALLOCATE
	g_PCSC.pfnSCardFreeMemory = nullptr;
	g_SCardAutoAllocate = FALSE;
#endif
#ifdef __APPLE__
	g_PnP_Notification = FALSE;
#endif
	return 1;
}

#endif
