/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2019, Joyent, Inc.
 * Copyright 2022 Oxide Computer Company
 */

#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/list.h>
#include <fcntl.h>
#include <fts.h>
#include <errno.h>
#include <strings.h>
#include <unistd.h>
#include <upanic.h>
#include <sys/debug.h>
#include <sys/filio.h>
#include <sys/usb/clients/ccid/uccid.h>

#include <winscard.h>

/*
 * Implementation of the PCSC library leveraging the uccid framework.
 */

typedef struct pcsc_hdl {
	hrtime_t pcsc_create_time;
	list_t pcsc_autoalloc;
	list_t pcsc_cards;
} pcsc_hdl_t;

typedef struct pcsc_card {
	list_node_t pcc_link;
	pcsc_hdl_t *pcc_hdl;
	int pcc_fd;
	char *pcc_name;
	size_t pcc_namelen;
} pcsc_card_t;

typedef struct pcsc_mem {
	list_node_t pcm_link;
	void *pcm_buf;
} pcsc_mem_t;

/*
 * Required globals
 */
SCARD_IO_REQUEST g_rgSCardT0Pci = {
	SCARD_PROTOCOL_T0,
	0
};

SCARD_IO_REQUEST g_rgSCardT1Pci = {
	SCARD_PROTOCOL_T1,
	0
};

SCARD_IO_REQUEST g_rgSCardRawPci = {
	SCARD_PROTOCOL_RAW,
	0
};

const char *
pcsc_stringify_error(const LONG err)
{
	switch (err) {
	case SCARD_S_SUCCESS:
		return ("no error");
	case SCARD_F_INTERNAL_ERROR:
		return ("internal error");
	case SCARD_E_CANCELLED:
		return ("request cancelled");
	case SCARD_E_INVALID_HANDLE:
		return ("invalid handle");
	case SCARD_E_INVALID_PARAMETER:
		return ("invalid parameter");
	case SCARD_E_NO_MEMORY:
		return ("no memory");
	case SCARD_E_INSUFFICIENT_BUFFER:
		return ("buffer was insufficiently sized");
	case SCARD_E_INVALID_VALUE:
		return ("invalid value passed");
	case SCARD_E_UNKNOWN_READER:
		return ("unknown reader");
	case SCARD_E_TIMEOUT:
		return ("timeout occurred");
	case SCARD_E_SHARING_VIOLATION:
		return ("sharing violation");
	case SCARD_E_NO_SMARTCARD:
		return ("no smartcard present");
	case SCARD_E_UNKNOWN_CARD:
		return ("unknown ICC");
	case SCARD_E_PROTO_MISMATCH:
		return ("protocol mismatch");
	case SCARD_F_COMM_ERROR:
		return ("communication error");
	case SCARD_F_UNKNOWN_ERROR:
		return ("unknown error");
	case SCARD_E_READER_UNAVAILABLE:
		return ("reader unavailable");
	case SCARD_E_NO_SERVICE:
		return ("service error");
	case SCARD_E_UNSUPPORTED_FEATURE:
		return ("ICC requires unsupported feature");
	case SCARD_E_NO_READERS_AVAILABLE:
		return ("no readers avaiable");
	case SCARD_W_UNSUPPORTED_CARD:
		return ("ICC unsupported");
	case SCARD_W_UNPOWERED_CARD:
		return ("ICC is not powered");
	case SCARD_W_RESET_CARD:
		return ("ICC was reset");
	case SCARD_W_REMOVED_CARD:
		return ("ICC has been removed");
	default:
		return ("unknown error");
	}
}

/*
 * Allocate a buffer of size "len" for use with an SCARD_AUTOALLOCATE
 * parameter.  Each automatically allocated buffer must be appended to the
 * context buffer list so that it can be freed during the call to
 * SCardReleaseContext().
 */
static void *
pcsc_mem_alloc(pcsc_hdl_t *hdl, size_t len)
{
	pcsc_mem_t *mem;

	if ((mem = malloc(sizeof (*mem))) == NULL) {
		return (NULL);
	}

	if ((mem->pcm_buf = malloc(len)) == NULL) {
		free(mem);
		return (NULL);
	}
	list_link_init(&mem->pcm_link);

	/*
	 * Put the buffer on the per-context list:
	 */
	list_insert_tail(&hdl->pcsc_autoalloc, mem);

	return (mem->pcm_buf);
}

static void
pcsc_mem_free(pcsc_hdl_t *hdl, void *buf)
{
	for (pcsc_mem_t *mem = list_head(&hdl->pcsc_autoalloc); mem != NULL;
	    mem = list_next(&hdl->pcsc_autoalloc, mem)) {
		if (mem->pcm_buf == buf) {
			list_remove(&hdl->pcsc_autoalloc, mem);
			free(mem->pcm_buf);
			free(mem);
			return;
		}
	}

	char msg[512];
	(void) snprintf(msg, sizeof (msg), "freed buffer %p not in context %p",
	    buf, hdl);
	upanic(msg, strlen(msg));
}

static pcsc_card_t *
pcsc_card_alloc(pcsc_hdl_t *hdl, const char *reader)
{
	pcsc_card_t *card;

	if ((card = malloc(sizeof (*card))) == NULL) {
		return (NULL);
	}
	card->pcc_hdl = hdl;
	card->pcc_fd = -1;
	list_link_init(&card->pcc_link);

	/*
	 * The reader name is returned as a multi-string, which means we need
	 * the regular C string and then an additional null termination byte to
	 * end the list of strings:
	 */
	card->pcc_namelen = strlen(reader) + 2;
	if ((card->pcc_name = malloc(card->pcc_namelen)) == NULL) {
		free(card);
		return (NULL);
	}
	bcopy(reader, card->pcc_name, card->pcc_namelen - 1);
	card->pcc_name[card->pcc_namelen - 1] = '\0';

	/*
	 * Insert the card handle into the per-context list so that we can free
	 * them later during SCardReleaseContext().
	 */
	list_insert_tail(&hdl->pcsc_cards, card);

	return (card);
}

static void
pcsc_card_free(pcsc_card_t *card)
{
	if (card == NULL) {
		return;
	}

	if (card->pcc_fd >= 0) {
		(void) close(card->pcc_fd);
	}

	/*
	 * Remove the card handle from the per-context list:
	 */
	pcsc_hdl_t *hdl = card->pcc_hdl;
	list_remove(&hdl->pcsc_cards, card);

	free(card->pcc_name);
	free(card);
}

/*
 * This is called when a caller wishes to open a new Library context.
 */
LONG
SCardEstablishContext(DWORD scope, LPCVOID unused0, LPCVOID unused1,
    LPSCARDCONTEXT outp)
{
	pcsc_hdl_t *hdl;

	if (outp == NULL) {
		return (SCARD_E_INVALID_PARAMETER);
	}

	if (scope != SCARD_SCOPE_SYSTEM) {
		return (SCARD_E_INVALID_VALUE);
	}

	hdl = calloc(1, sizeof (pcsc_hdl_t));
	if (hdl == NULL) {
		return (SCARD_E_NO_MEMORY);
	}
	list_create(&hdl->pcsc_autoalloc, sizeof (pcsc_mem_t),
	    offsetof(pcsc_mem_t, pcm_link));
	list_create(&hdl->pcsc_cards, sizeof (pcsc_card_t),
	    offsetof(pcsc_card_t, pcc_link));

	hdl->pcsc_create_time = gethrtime();
	*outp = hdl;
	return (SCARD_S_SUCCESS);
}

bool
pcsc_valid_context(SCARDCONTEXT hdl)
{
	/*
	 * On some other platforms, the context handle is a signed integer.
	 * Some software has been observed to use -1 as an invalid handle
	 * sentinel value, so we need to explicitly handle that here.
	 */
	return (hdl != NULL && (uintptr_t)hdl != UINTPTR_MAX);
}

LONG
SCardIsValidContext(SCARDCONTEXT hdl)
{
	if (!pcsc_valid_context(hdl)) {
		return (SCARD_E_INVALID_HANDLE);
	}

	return (SCARD_S_SUCCESS);
}

/*
 * This is called to free a library context from a client.
 */
LONG
SCardReleaseContext(SCARDCONTEXT arg)
{
	if (!pcsc_valid_context(arg)) {
		return (SCARD_E_INVALID_HANDLE);
	}

	/*
	 * Free any SCARD_AUTOALLOCATE memory now.
	 */
	pcsc_hdl_t *hdl = arg;
	pcsc_mem_t *mem;
	while ((mem = list_head(&hdl->pcsc_autoalloc)) != NULL) {
		pcsc_mem_free(hdl, mem->pcm_buf);
	}
	list_destroy(&hdl->pcsc_autoalloc);

	/*
	 * Free any card handles that were not explicitly freed:
	 */
	pcsc_card_t *card;
	while ((card = list_head(&hdl->pcsc_cards)) != NULL) {
		pcsc_card_free(card);
	}
	list_destroy(&hdl->pcsc_cards);

	free(hdl);
	return (SCARD_S_SUCCESS);
}

/*
 * This is called to release memory allocated by the library. No, it doesn't
 * make sense to take a const pointer when being given memory to free. It just
 * means we have to cast it, but remember: this isn't our API.
 */
LONG
SCardFreeMemory(SCARDCONTEXT hdl, LPCVOID mem)
{
	if (!pcsc_valid_context(hdl)) {
		return (SCARD_E_INVALID_HANDLE);
	}

	pcsc_mem_free(hdl, (void *)mem);
	return (SCARD_S_SUCCESS);
}

/*
 * This is called by a caller to get a list of readers that exist in the system.
 * If lenp is set to SCARD_AUTOALLOCATE, then we are responsible for dealing
 * with this memory.
 */
LONG
SCardListReaders(SCARDCONTEXT arg, LPCSTR groups, LPSTR bufp, LPDWORD lenp)
{
	pcsc_hdl_t *hdl = arg;
	FTS *fts;
	FTSENT *ent;
	char *const root[] = { "/dev/ccid", NULL };
	char *ubuf;
	char **readers;
	uint32_t len, ulen, npaths, nalloc, off, i;
	int ret;

	if (!pcsc_valid_context(hdl)) {
		return (SCARD_E_INVALID_HANDLE);
	}

	if (groups != NULL || lenp == NULL) {
		return (SCARD_E_INVALID_PARAMETER);
	}

	fts = fts_open(root, FTS_LOGICAL | FTS_NOCHDIR, NULL);
	if (fts == NULL) {
		switch (errno) {
		case ENOENT:
		case ENOTDIR:
			return (SCARD_E_NO_READERS_AVAILABLE);
		case ENOMEM:
		case EAGAIN:
			return (SCARD_E_NO_MEMORY);
		default:
			return (SCARD_E_NO_SERVICE);
		}
	}

	npaths = nalloc = 0;
	/*
	 * Account for the NUL we'll have to place at the end of this.
	 */
	len = 1;
	readers = NULL;
	while ((ent = fts_read(fts)) != NULL) {
		size_t plen;

		if (ent->fts_level != 2 || ent->fts_info == FTS_DP)
			continue;

		if (ent->fts_info == FTS_ERR || ent->fts_info == FTS_NS)
			continue;

		if (S_ISCHR(ent->fts_statp->st_mode) == 0)
			continue;

		plen = strlen(ent->fts_path) + 1;
		if (UINT32_MAX - len <= plen) {
			/*
			 * I mean, it's true. But I wish I could just give you
			 * EOVERFLOW.
			 */
			ret = SCARD_E_INSUFFICIENT_BUFFER;
			goto out;
		}

		if (npaths == nalloc) {
			char **tmp;

			nalloc += 8;
			tmp = reallocarray(readers, nalloc, sizeof (char *));
			if (tmp == NULL) {
				ret = SCARD_E_NO_MEMORY;
				goto out;
			}
			readers = tmp;
		}
		readers[npaths] = strdup(ent->fts_path);
		npaths++;
		len += plen;
	}

	if (npaths == 0) {
		ret = SCARD_E_NO_READERS_AVAILABLE;
		goto out;
	}

	ulen = *lenp;
	*lenp = len;
	if (ulen != SCARD_AUTOALLOCATE) {
		if (bufp == NULL) {
			ret = SCARD_S_SUCCESS;
			goto out;
		}

		if (ulen < len) {
			ret = SCARD_E_INSUFFICIENT_BUFFER;
			goto out;
		}

		ubuf = bufp;
	} else {
		char **bufpp;
		if (bufp == NULL) {
			ret = SCARD_E_INVALID_PARAMETER;
			goto out;
		}

		if ((ubuf = pcsc_mem_alloc(hdl, ulen)) == NULL) {
			ret = SCARD_E_NO_MEMORY;
			goto out;
		}

		bufpp = (void *)bufp;
		*bufpp = ubuf;
	}
	ret = SCARD_S_SUCCESS;

	for (off = 0, i = 0; i < npaths; i++) {
		size_t slen = strlen(readers[i]) + 1;
		bcopy(readers[i], ubuf + off, slen);
		off += slen;
		VERIFY3U(off, <=, len);
	}
	VERIFY3U(off, ==, len - 1);
	ubuf[off] = '\0';
out:
	for (i = 0; i < npaths; i++) {
		free(readers[i]);
	}
	free(readers);
	(void) fts_close(fts);
	return (ret);
}

static LONG
uccid_status_helper(int fd, DWORD prots, uccid_cmd_status_t *ucs)
{
	/*
	 * Get the status of this slot and find out information about the slot.
	 * We need to see if there's an ICC present and if it matches the
	 * current protocol. If not, then we have to fail this.
	 */
	bzero(ucs, sizeof (uccid_cmd_status_t));
	ucs->ucs_version = UCCID_CURRENT_VERSION;
	if (ioctl(fd, UCCID_CMD_STATUS, ucs) != 0) {
		return (SCARD_F_UNKNOWN_ERROR);
	}

	if ((ucs->ucs_status & UCCID_STATUS_F_CARD_PRESENT) == 0) {
		return (SCARD_W_REMOVED_CARD);
	}

	if ((ucs->ucs_status & UCCID_STATUS_F_CARD_ACTIVE) == 0) {
		return (SCARD_W_UNPOWERED_CARD);
	}

	if ((ucs->ucs_status & UCCID_STATUS_F_PARAMS_VALID) == 0) {
		return (SCARD_W_UNSUPPORTED_CARD);
	}

	if ((ucs->ucs_prot & prots) == 0) {
		return (SCARD_E_PROTO_MISMATCH);
	}

	return (0);
}

LONG
SCardConnect(SCARDCONTEXT hdl, LPCSTR reader, DWORD mode, DWORD prots,
    LPSCARDHANDLE iccp, LPDWORD protp)
{
	LONG ret;
	uccid_cmd_status_t ucs;
	pcsc_card_t *card;

	if (!pcsc_valid_context(hdl)) {
		return (SCARD_E_INVALID_HANDLE);
	}

	if (reader == NULL) {
		return (SCARD_E_UNKNOWN_READER);
	}

	if (iccp == NULL || protp == NULL) {
		return (SCARD_E_INVALID_PARAMETER);
	}

	if (mode != SCARD_SHARE_SHARED) {
		return (SCARD_E_INVALID_VALUE);
	}

	if ((prots & ~(SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1 |
	    SCARD_PROTOCOL_RAW | SCARD_PROTOCOL_T15)) != 0) {
		return (SCARD_E_INVALID_VALUE);
	}

	if ((prots & (SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1)) == 0) {
		return (SCARD_E_UNSUPPORTED_FEATURE);
	}

	if ((card = pcsc_card_alloc(hdl, reader)) == NULL) {
		pcsc_card_free(card);
		return (SCARD_E_NO_MEMORY);
	}

	if ((card->pcc_fd = open(reader, O_RDWR)) < 0) {
		pcsc_card_free(card);
		switch (errno) {
		case ENOENT:
			return (SCARD_E_UNKNOWN_READER);
		default:
			return (SCARD_F_UNKNOWN_ERROR);
		}
	}

	if ((ret = uccid_status_helper(card->pcc_fd, prots, &ucs)) != 0) {
		pcsc_card_free(card);
		return (ret);
	}

	*protp = ucs.ucs_prot;
	*iccp = card;
	return (SCARD_S_SUCCESS);
}

/*
 * The Windows documentation suggests that all of the input/output arguments
 * (other than the handle) are effectively optional.
 */
LONG
SCardStatus(SCARDHANDLE arg, LPSTR readerp, LPDWORD readerlenp,
    LPDWORD statep, LPDWORD protop, LPBYTE atrp, LPDWORD atrlenp)
{
	pcsc_card_t *card = arg;
	pcsc_hdl_t *hdl = card->pcc_hdl;
	LONG ret = SCARD_S_SUCCESS;

	if (statep == NULL && protop == NULL && atrlenp == NULL) {
		/*
		 * There is no need to perform the status ioctl.
		 */
		goto name;
	}

	uccid_cmd_status_t ucs = { .ucs_version = UCCID_CURRENT_VERSION };
	if (ioctl(card->pcc_fd, UCCID_CMD_STATUS, &ucs) != 0) {
		VERIFY3S(errno, ==, ENODEV);
		ret = SCARD_E_READER_UNAVAILABLE;
		goto out;
	}

	if (statep != NULL) {
		if (!(ucs.ucs_status & UCCID_STATUS_F_CARD_PRESENT)) {
			*statep = SCARD_ABSENT;
		} else if (ucs.ucs_status & UCCID_STATUS_F_CARD_ACTIVE) {
			if (ucs.ucs_status & UCCID_STATUS_F_PARAMS_VALID) {
				*statep = SCARD_SPECIFIC;
			} else {
				*statep = SCARD_POWERED;
			}
		} else {
			*statep = SCARD_PRESENT;
		}
	}

	if (protop != NULL) {
		if (ucs.ucs_status & UCCID_STATUS_F_PARAMS_VALID) {
			switch (ucs.ucs_prot) {
			case UCCID_PROT_T0:
				*protop = SCARD_PROTOCOL_T0;
				break;
			case UCCID_PROT_T1:
				*protop = SCARD_PROTOCOL_T1;
				break;
			default:
				*protop = SCARD_PROTOCOL_UNDEFINED;
				break;
			}
		} else {
			/*
			 * If SCARD_SPECIFIC is not returned as the card
			 * state, this value is not considered meaningful.
			 */
			*protop = SCARD_PROTOCOL_UNDEFINED;
		}
	}

	if (atrlenp != NULL) {
		uint8_t *ubuf;
		uint32_t len = *atrlenp;
		if (len != SCARD_AUTOALLOCATE) {
			if (len < ucs.ucs_atrlen) {
				*atrlenp = ucs.ucs_atrlen;
				ret = SCARD_E_INSUFFICIENT_BUFFER;
				goto out;
			}

			if (atrp == NULL) {
				ret = SCARD_E_INVALID_PARAMETER;
				goto out;
			}

			ubuf = atrp;
		} else {
			if ((ubuf = pcsc_mem_alloc(hdl, ucs.ucs_atrlen)) ==
			    NULL) {
				ret = SCARD_E_NO_MEMORY;
				goto out;
			}

			*((LPBYTE *)atrp) = ubuf;
		}

		bcopy(ucs.ucs_atr, ubuf, ucs.ucs_atrlen);
		*atrlenp = ucs.ucs_atrlen;
	}

name:
	if (readerlenp != NULL) {
		char *ubuf;
		uint32_t rlen = *readerlenp;
		if (rlen != SCARD_AUTOALLOCATE) {
			if (rlen < card->pcc_namelen) {
				*readerlenp = card->pcc_namelen;
				ret = SCARD_E_INSUFFICIENT_BUFFER;
				goto out;
			}

			if (readerp == NULL) {
				ret = SCARD_E_INVALID_PARAMETER;
				goto out;
			}

			ubuf = readerp;
		} else {
			if ((ubuf = pcsc_mem_alloc(hdl, card->pcc_namelen)) ==
			    NULL) {
				ret = SCARD_E_NO_MEMORY;
				goto out;
			}

			*((LPSTR *)readerp) = ubuf;
		}

		/*
		 * We stored the reader name as a multi-string in
		 * pcsc_card_alloc(), so we can just copy out the whole value
		 * here without further modification:
		 */
		bcopy(card->pcc_name, ubuf, card->pcc_namelen);
	}

out:
	return (ret);
}

LONG
SCardDisconnect(SCARDHANDLE arg, DWORD disposition)
{
	pcsc_card_t *card = arg;

	if (arg == NULL) {
		return (SCARD_E_INVALID_HANDLE);
	}

	switch (disposition) {
	case SCARD_RESET_CARD: {
		/*
		 * To reset the card, we first need to get exclusive access to
		 * the card.
		 */
		uccid_cmd_txn_begin_t txnbegin = {
			.uct_version = UCCID_CURRENT_VERSION,
		};
		if (ioctl(card->pcc_fd, UCCID_CMD_TXN_BEGIN, &txnbegin) != 0) {
			VERIFY3S(errno, !=, EFAULT);

			switch (errno) {
			case ENODEV:
				/*
				 * If the card is no longer present, we cannot
				 * reset it.
				 */
				goto close;
			case EEXIST:
				break;
			case EBUSY:
				return (SCARD_E_SHARING_VIOLATION);
			default:
				return (SCARD_F_UNKNOWN_ERROR);
			}
		}

		/*
		 * Once we have begun the transaction, we can end it
		 * immediately while requesting a reset before the next
		 * transaction.
		 */
		uccid_cmd_txn_end_t txnend = {
			.uct_version = UCCID_CURRENT_VERSION,
			.uct_flags = UCCID_TXN_END_RESET,
		};
		if (ioctl(card->pcc_fd, UCCID_CMD_TXN_END, &txnend) != 0) {
			VERIFY3S(errno, !=, EFAULT);

			switch (errno) {
			case ENODEV:
				goto close;
			default:
				return (SCARD_F_UNKNOWN_ERROR);
			}
		}
	}
	case SCARD_LEAVE_CARD:
		break;
	default:
		return (SCARD_E_INVALID_VALUE);
	}

close:
	if (close(card->pcc_fd) != 0) {
		return (SCARD_F_UNKNOWN_ERROR);
	}
	card->pcc_fd = -1;

	pcsc_card_free(card);
	return (SCARD_S_SUCCESS);
}

LONG
SCardBeginTransaction(SCARDHANDLE arg)
{
	uccid_cmd_txn_begin_t txn;
	pcsc_card_t *card = arg;

	if (card == NULL) {
		return (SCARD_E_INVALID_HANDLE);
	}

	/*
	 * According to the Windows documentation, this function "waits for the
	 * completion of all other transactions before it begins".  Software in
	 * the wild has been observed to be confused by the
	 * SCARD_E_SHARING_VIOLATION condition we would previously return if
	 * the card was already in use.  As a compatibility measure, we omit
	 * the UCCID_TXN_DONT_BLOCK flag so that the kernel will sleep until
	 * the card is no longer busy before returning.
	 */
	bzero(&txn, sizeof (uccid_cmd_txn_begin_t));
	txn.uct_version = UCCID_CURRENT_VERSION;

	if (ioctl(card->pcc_fd, UCCID_CMD_TXN_BEGIN, &txn) != 0) {
		VERIFY3S(errno, !=, EFAULT);
		switch (errno) {
		case ENODEV:
			return (SCARD_E_READER_UNAVAILABLE);
		case EEXIST:
			/*
			 * This is an odd case. It's used to tell us that we
			 * already have it. For now, just treat it as success.
			 */
			return (SCARD_S_SUCCESS);
		case EBUSY:
			return (SCARD_E_SHARING_VIOLATION);
		/*
		 * EINPROGRESS is a weird case. It means that we were trying to
		 * grab a hold while another instance using the same handle was.
		 * For now, treat it as an unknown error.
		 */
		case EINPROGRESS:
		case EINTR:
		default:
			return (SCARD_F_UNKNOWN_ERROR);
		}
	}
	return (SCARD_S_SUCCESS);
}

LONG
SCardEndTransaction(SCARDHANDLE arg, DWORD state)
{
	uccid_cmd_txn_end_t txn;
	pcsc_card_t *card = arg;

	if (card == NULL) {
		return (SCARD_E_INVALID_HANDLE);
	}

	bzero(&txn, sizeof (uccid_cmd_txn_end_t));
	txn.uct_version = UCCID_CURRENT_VERSION;

	switch (state) {
	case SCARD_LEAVE_CARD:
		txn.uct_flags = UCCID_TXN_END_RELEASE;
		break;
	case SCARD_RESET_CARD:
		txn.uct_flags = UCCID_TXN_END_RESET;
		break;
	case SCARD_UNPOWER_CARD:
	case SCARD_EJECT_CARD:
	default:
		return (SCARD_E_INVALID_VALUE);
	}

	if (ioctl(card->pcc_fd, UCCID_CMD_TXN_END, &txn) != 0) {
		VERIFY3S(errno, !=, EFAULT);
		switch (errno) {
		case ENODEV:
			return (SCARD_E_READER_UNAVAILABLE);
		case ENXIO:
			return (SCARD_E_SHARING_VIOLATION);
		default:
			return (SCARD_F_UNKNOWN_ERROR);
		}
	}
	return (SCARD_S_SUCCESS);
}

LONG
SCardReconnect(SCARDHANDLE arg, DWORD mode, DWORD prots, DWORD init,
    LPDWORD protp)
{
	uccid_cmd_status_t ucs;
	pcsc_card_t *card = arg;
	LONG ret;

	if (card == NULL) {
		return (SCARD_E_INVALID_HANDLE);
	}

	if (protp == NULL) {
		return (SCARD_E_INVALID_PARAMETER);
	}

	if (mode != SCARD_SHARE_SHARED) {
		return (SCARD_E_INVALID_VALUE);
	}

	if ((prots & ~(SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1 |
	    SCARD_PROTOCOL_RAW | SCARD_PROTOCOL_T15)) != 0) {
		return (SCARD_E_INVALID_VALUE);
	}

	if ((prots & (SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1)) == 0) {
		return (SCARD_E_UNSUPPORTED_FEATURE);
	}

	if (init != SCARD_LEAVE_CARD) {
		return (SCARD_E_INVALID_VALUE);
	}

	if ((ret = uccid_status_helper(card->pcc_fd, prots, &ucs)) != 0)
		return (ret);

	*protp = ucs.ucs_prot;
	return (SCARD_S_SUCCESS);
}

LONG
SCardTransmit(SCARDHANDLE arg, const SCARD_IO_REQUEST *sendreq,
    LPCBYTE sendbuf, DWORD sendlen, SCARD_IO_REQUEST *recvreq, LPBYTE recvbuf,
    LPDWORD recvlenp)
{
	int len;
	ssize_t ret;
	pcsc_card_t *card = arg;

	if (card == NULL) {
		return (SCARD_E_INVALID_HANDLE);
	}

	/*
	 * Ignore sendreq / recvreq.
	 */
	if (sendbuf == NULL || recvbuf == NULL || recvlenp == NULL) {
		return (SCARD_E_INVALID_PARAMETER);
	}

	/*
	 * The CCID write will always consume all data or none.
	 */
	ret = write(card->pcc_fd, sendbuf, sendlen);
	if (ret == -1) {
		switch (errno) {
		case E2BIG:
			return (SCARD_E_INVALID_PARAMETER);
		case ENODEV:
			return (SCARD_E_READER_UNAVAILABLE);
		case EACCES:
		case EBUSY:
			return (SCARD_E_SHARING_VIOLATION);
		case ENXIO:
			return (SCARD_W_REMOVED_CARD);
		case EFAULT:
			return (SCARD_E_INVALID_PARAMETER);
		case ENOMEM:
		default:
			return (SCARD_F_UNKNOWN_ERROR);
		}
	}
	ASSERT3S(ret, ==, sendlen);

	/*
	 * Now, we should be able to block in read.
	 */
	ret = read(card->pcc_fd, recvbuf, *recvlenp);
	if (ret == -1) {
		switch (errno) {
		case EINVAL:
		case EOVERFLOW:
			/*
			 * This means that we need to update len with the real
			 * one.
			 */
			if (ioctl(card->pcc_fd, FIONREAD, &len) != 0) {
				return (SCARD_F_UNKNOWN_ERROR);
			}
			*recvlenp = len;
			return (SCARD_E_INSUFFICIENT_BUFFER);
		case ENODEV:
			return (SCARD_E_READER_UNAVAILABLE);
		case EACCES:
		case EBUSY:
			return (SCARD_E_SHARING_VIOLATION);
		case EFAULT:
			return (SCARD_E_INVALID_PARAMETER);
		case ENODATA:
		default:
			return (SCARD_F_UNKNOWN_ERROR);
		}
	}

	*recvlenp = ret;

	return (SCARD_S_SUCCESS);
}