/*****************************************************************
/
/ File   :   ifdhandler.c
/ Authors :   David Corcoran <corcoran@linuxnet.com>
/             Jean-Luc Giraud <jl.giraud@free.fr>
/             Ludovic Rousseau <ludovic.rousseau@free.fr>
/ Date   :   April 9, 2001, 2002, 2003, 2004
/ Purpose:   This provides reader specific low-level calls
/            for the GemPC family of Gemplus. The function
/            stubs were written by D. Corcoran, the GemCore
/            +specific code was added by JL Giraud.
/            This module implements the command level of GemCore
/            See http://www.linuxnet.com for more information.
/ License:   See file COPYING.BSD
/
/ $Id: ifdhandler.c,v 1.31 2012/04/08 19:09:47 rousseau Exp $
/
******************************************************************/

#include <stdio.h>
#include <string.h>
#ifdef HAVE_PTHREAD_H
#include <pthread.h>
#endif

#include "gempc_ifdhandler.h"
#include "Config.h"
#include "GemCore.h"
#include "GCUtils.h"
#include "GCCmds.h"
#include "GCdebug.h"

#if GEMPC==410
#include "../GemPC410/GemPC410Utils.h"
#endif

#if GEMPC==430
#include "../GemPC430/GemPC430Utils.h"
#endif

#ifndef __CONFIG_H__
#error "GEMPC must be configured (set to 410 or 430)"
#endif

/* Array of structures to hold the ATR and other state value of each slot */
static GCoreDesc pgSlots[PCSCLITE_MAX_READERS];


RESPONSECODE IFDHCreateChannelByName(DWORD Lun, LPSTR lpcDevice)
{
	DEBUG_INFO3("lun: %lX, device: %s", Lun, lpcDevice);

	if (iLunCheck(Lun))
		return IFD_COMMUNICATION_ERROR;

	/* Reset ATR buffer */
	pgSlots[LunToReaderIndex(Lun)].nATRLength = 0;
	*pgSlots[LunToReaderIndex(Lun)].pcATRBuffer = '\0';

	/* Reset PowerFlags */
	pgSlots[LunToReaderIndex(Lun)].bPowerFlags = POWERFLAGS_RAZ;

	if (OpenPortByName(Lun, lpcDevice) != IFD_SUCCESS)
	{
		DEBUG_CRITICAL("OpenPort failed");
		return IFD_COMMUNICATION_ERROR;
	}

	return IFD_SUCCESS;
} /* IFDHCreateChannelByName */


RESPONSECODE IFDHCreateChannel(DWORD Lun, DWORD Channel)
{
	/*
	 * Lun - Logical Unit Number, use this for multiple card slots or
	 * multiple readers. 0xXXXXYYYY - XXXX multiple readers, YYYY multiple
	 * slots. The resource manager will set these automatically.  By
	 * default the resource manager loads a new instance of the driver so
	 * if your reader does not have more than one smartcard slot then
	 * ignore the Lun in all the functions. Future versions of PC/SC might
	 * support loading multiple readers through one instance of the driver
	 * in which XXXX would be important to implement if you want this.
	 */

	/*
	 * Channel - Channel ID.  This is denoted by the following:
	 * 0x000001 - /dev/pcsc/1
	 * 0x000002 - /dev/pcsc/2
	 * 0x000003 - /dev/pcsc/3
	 * 0x000004 - /dev/pcsc/4
	 *
	 * USB readers may choose to ignore this parameter and query the bus
	 * for the particular reader.
	 */

	/*
	 * This function is required to open a communications channel to the
	 * port listed by Channel.  For example, the first serial reader on
	 * COM1 would link to /dev/pcsc/1 which would be a sym link to
	 * /dev/ttyS0 on some machines This is used to help with intermachine
	 * independance.
	 *
	 * Once the channel is opened the reader must be in a state in which
	 * it is possible to query IFDHICCPresence() for card status.
	 *
	 * returns:
	 *
	 * IFD_SUCCESS IFD_COMMUNICATION_ERROR
	 */
	RESPONSECODE return_value = IFD_SUCCESS;

	DEBUG_INFO3("lun: %lX, channel: %ld", Lun, Channel);

	if (iLunCheck(Lun))
		return IFD_COMMUNICATION_ERROR;

	/* Reset ATR buffer */
	pgSlots[LunToReaderIndex(Lun)].nATRLength = 0;
	*pgSlots[LunToReaderIndex(Lun)].pcATRBuffer = '\0';

	/* Reset PowerFlags */
	pgSlots[LunToReaderIndex(Lun)].bPowerFlags =
		POWERFLAGS_RAZ;

	if (OpenPort(Lun, Channel) != IFD_SUCCESS)
	{
		DEBUG_CRITICAL("OpenReader failed");
		return_value = IFD_COMMUNICATION_ERROR;
	}

	return return_value;
} /* IFDHCreateChannel */


RESPONSECODE IFDHCloseChannel(DWORD Lun)
{
	/*
	 * This function should close the reader communication channel for the
	 * particular reader.  Prior to closing the communication channel the
	 * reader should make sure the card is powered down and the terminal
	 * is also powered down.
	 *
	 * returns:
	 *
	 * IFD_SUCCESS IFD_COMMUNICATION_ERROR
	 */

	DEBUG_INFO2("lun: %lX", Lun);

	if (iLunCheck(Lun))
		return IFD_COMMUNICATION_ERROR;

	GCCmdPowerDown(Lun);
	/* No reader status check, if it failed, what can you do ? :) */

	ClosePort(Lun);

	return IFD_SUCCESS;
} /* IFDHCloseChannel */


RESPONSECODE IFDHGetCapabilities(DWORD Lun, DWORD Tag,
	PDWORD Length, PUCHAR Value)
{
	/*
	 * This function should get the slot/card capabilities for a
	 * particular slot/card specified by Lun.  Again, if you have only 1
	 * card slot and don't mind loading a new driver for each reader then
	 * ignore Lun.
	 *
	 * Tag - the tag for the information requested example: TAG_IFD_ATR -
	 * return the Atr and it's size (required). these tags are defined in
	 * ifdhandler.h
	 *
	 * Length - the length of the returned data Value - the value of the
	 * data
	 *
	 * returns:
	 *
	 * IFD_SUCCESS IFD_ERROR_TAG
	 */

	DEBUG_INFO3("lun: %lX, tag: %lX", Lun, Tag);

	if (iLunCheck(Lun))
		return IFD_COMMUNICATION_ERROR;

	switch (Tag)
	{
		case TAG_IFD_ATR:
			/* If Length is not zero, powerICC has been performed.
			 * Otherwise, return NULL pointer
			 * Buffer size is stored in *Length */
			*Length = (*Length < pgSlots[LunToReaderIndex(Lun)].nATRLength) ?
				*Length : pgSlots[LunToReaderIndex(Lun)].nATRLength;

			if (*Length)
				memcpy(Value, pgSlots[LunToReaderIndex(Lun)].pcATRBuffer,
					*Length);
			break;

		case TAG_IFD_SIMULTANEOUS_ACCESS:
			if (*Length >= 1)
			{
				*Length = 1;
				*Value = PCSCLITE_MAX_READERS;
			}
			break;

		default:
			return IFD_ERROR_TAG;
	}
	return IFD_SUCCESS;
} /* IFDHGetCapabilities */


RESPONSECODE IFDHSetCapabilities(DWORD Lun, DWORD Tag,
	DWORD Length, PUCHAR Value)
{
	/*
	 * This function should set the slot/card capabilities for a
	 * particular slot/card specified by Lun.  Again, if you have only 1
	 * card slot and don't mind loading a new driver for each reader then
	 * ignore Lun.
	 *
	 * Tag - the tag for the information needing set
	 *
	 * Length - the length of the returned data Value - the value of the
	 * data
	 *
	 * returns:
	 *
	 * IFD_SUCCESS IFD_ERROR_TAG IFD_ERROR_SET_FAILURE
	 * IFD_ERROR_VALUE_READ_ONLY
	 */

	/* By default, say it worked */

	DEBUG_PERIODIC3("lun: %lX, tag: %lX", Lun, Tag);
	(void)Length;
	(void)Value;

	if (iLunCheck(Lun))
		return IFD_COMMUNICATION_ERROR;

	return IFD_SUCCESS;
} /* IFDHSetCapabilities */


RESPONSECODE IFDHSetProtocolParameters(DWORD Lun, DWORD Protocol,
	UCHAR Flags, UCHAR PTS1, UCHAR PTS2, UCHAR PTS3)
{
	/*
	 * This function should set the PTS of a particular card/slot using
	 * the three PTS parameters sent
	 *
	 * Protocol - 0 .... 14 T=0 .... T=14 Flags - Logical OR of possible
	 * values: IFD_NEGOTIATE_PTS1 IFD_NEGOTIATE_PTS2 IFD_NEGOTIATE_PTS3 to
	 * determine which PTS values to negotiate. PTS1,PTS2,PTS3 - PTS
	 * Values.
	 *
	 * returns:
	 *
	 * IFD_SUCCESS IFD_ERROR_PTS_FAILURE IFD_COMMUNICATION_ERROR
	 * IFD_PROTOCOL_NOT_SUPPORTED
	 */

	DEBUG_INFO2("lun: %lX", Lun);
	(void)Protocol;
	(void)Flags;
	(void)PTS1;
	(void)PTS2;
	(void)PTS3;

	if (iLunCheck(Lun))
		return IFD_COMMUNICATION_ERROR;

	return IFD_SUCCESS;
} /* IFDHSetProtocolParameters */


RESPONSECODE IFDHPowerICC(DWORD Lun, DWORD Action,
	PUCHAR Atr, PDWORD AtrLength)
{
	/*
	 * This function controls the power and reset signals of the smartcard
	 * reader at the particular reader/slot specified by Lun.
	 *
	 * Action - Action to be taken on the card.
	 *
	 * IFD_POWER_UP - Power and reset the card if not done so (store the
	 * ATR and return it and it's length).
	 *
	 * IFD_POWER_DOWN - Power down the card if not done already
	 * (Atr/AtrLength should be zero'd)
	 *
	 * IFD_RESET - Perform a quick reset on the card.  If the card is not
	 * powered power up the card.  (Store and return the Atr/Length)
	 *
	 * Atr - Answer to Reset of the card.  The driver is responsible for
	 * caching this value in case IFDHGetCapabilities is called requesting
	 * the ATR and it's length.  This should not exceed MAX_ATR_SIZE.
	 *
	 * AtrLength - Length of the Atr.  This should not exceed
	 * MAX_ATR_SIZE.
	 *
	 * Notes:
	 *
	 * Memory cards without an ATR should return IFD_SUCCESS on reset but
	 * the Atr should be zero'd and the length should be zero
	 *
	 * Reset errors should return zero for the AtrLength and return
	 * IFD_ERROR_POWER_ACTION.
	 *
	 * returns:
	 *
	 * IFD_SUCCESS IFD_ERROR_POWER_ACTION IFD_COMMUNICATION_ERROR
	 * IFD_NOT_SUPPORTED
	 */

	DWORD nlength;
	RESPONSECODE return_value = IFD_SUCCESS;
	UCHAR pcbuffer[RESP_BUF_SIZE];

	DEBUG_INFO2("lun: %lX", Lun);

	/* By default, assume it won't work :) */
	*AtrLength = 0;

	if (iLunCheck(Lun))
		return IFD_COMMUNICATION_ERROR;

	switch (Action)
	{
		case IFD_POWER_UP:
		case IFD_RESET:
			nlength = sizeof(pcbuffer);
			if ((return_value = GCCmdPowerUp(Lun, &nlength, pcbuffer)) != IFD_SUCCESS)
			{
				DEBUG_CRITICAL("PowerUp failed");
				goto end;
			}

			/* Power up successful, set state variable to memorise it */
			pgSlots[LunToReaderIndex(Lun)].bPowerFlags |= MASK_POWERFLAGS_PUP;
			pgSlots[LunToReaderIndex(Lun)].bPowerFlags &= ~MASK_POWERFLAGS_PDWN;

			/* Reset is returned, even if TCK is wrong */
			pgSlots[LunToReaderIndex(Lun)].nATRLength = *AtrLength =
				(nlength < MAX_ATR_SIZE) ? nlength : MAX_ATR_SIZE;
			memcpy(Atr, pcbuffer, *AtrLength);
			memcpy(pgSlots[LunToReaderIndex(Lun)].pcATRBuffer, pcbuffer,
				*AtrLength);

			break;

		case IFD_POWER_DOWN:
			/* Clear ATR buffer */
			pgSlots[LunToReaderIndex(Lun)].nATRLength =
				0;
			*pgSlots[LunToReaderIndex(Lun)].pcATRBuffer = '\0';
			/* Memorise the request */
			pgSlots[LunToReaderIndex(Lun)].bPowerFlags |= MASK_POWERFLAGS_PDWN;
			/* send the command */
			return_value = GCCmdPowerDown(Lun);
			break;

		default:
			DEBUG_CRITICAL("Action not supported");
			return_value = IFD_NOT_SUPPORTED;
	}
end:

	return return_value;
} /* IFDHPowerICC */


RESPONSECODE IFDHTransmitToICC(DWORD Lun, SCARD_IO_HEADER SendPci,
	PUCHAR TxBuffer, DWORD TxLength,
	PUCHAR RxBuffer, PDWORD RxLength, PSCARD_IO_HEADER RecvPci)
{
	/*
	 * This function performs an APDU exchange with the card/slot
	 * specified by Lun.  The driver is responsible for performing any
	 * protocol specific exchanges such as T=0/1 ... differences.  Calling
	 * this function will abstract all protocol differences.
	 *
	 * SendPci Protocol - 0, 1, .... 14 Length - Not used.
	 *
	 * TxBuffer - Transmit APDU example (0x00 0xA4 0x00 0x00 0x02 0x3F
	 * 0x00) TxLength - Length of this buffer. RxBuffer - Receive APDU
	 * example (0x61 0x14) RxLength - Length of the received APDU.  This
	 * function will be passed the size of the buffer of RxBuffer and this
	 * function is responsible for setting this to the length of the
	 * received APDU.  This should be ZERO on all errors.  The resource
	 * manager will take responsibility of zeroing out any temporary APDU
	 * buffers for security reasons.
	 *
	 * RecvPci Protocol - 0, 1, .... 14 Length - Not used.
	 *
	 * Notes: The driver is responsible for knowing what type of card it
	 * has.  If the current slot/card contains a memory card then this
	 * command should ignore the Protocol and use the MCT style commands
	 * for support for these style cards and transmit them appropriately.
	 * If your reader does not support memory cards or you don't want to
	 * then ignore this.
	 *
	 * RxLength should be set to zero on error.
	 *
	 * returns:
	 *
	 * IFD_SUCCESS IFD_COMMUNICATION_ERROR IFD_RESPONSE_TIMEOUT
	 * IFD_ICC_NOT_PRESENT IFD_PROTOCOL_NOT_SUPPORTED
	 */

	DWORD ntestlength;
	RESPONSECODE return_value = IFD_SUCCESS;	/* Assume it will work */
	DWORD protocol;

	DEBUG_INFO2("lun: %lX", Lun);
	(void)RecvPci;

	if (iLunCheck(Lun))
		return IFD_COMMUNICATION_ERROR;

	protocol = SendPci.Protocol;

	/* if the reader is in EMV mode use ISO_EXCHANGE instead of ISO_INPUT
	 * and ISO_OUTPUT. This is done with T=1 protocol */
	if (pgSlots[LunToReaderIndex(Lun)].bPowerFlags & MASK_POWERFLAGS_EMV)
		protocol = T_1;

	switch (protocol)
	{
		case T_0:
			/* Check if command is going to fit in buffer */
			if (CMD_BUF_SIZE < (GC_SIZE_CMD + TxLength))
			{
				/* Buffer too small, send an error */
				return_value = IFD_COMMUNICATION_ERROR;
				goto clean_up_and_return;
			}

			/* Check if this is an incoming or outgoing command
			 * Size should be command + one byte of length for
			 * an outgoing TPDU (CLA, INS, P1, P2, P3) */
			if (TxLength == (ISO_CMD_SIZE + ISO_LENGTH_SIZE))
			{
				return_value = gemcore_ISO_OUTPUT_processing(Lun,
					TxBuffer, TxLength, RxBuffer, RxLength);

				break;
			}
			else
			{
				/* just (CLA, INS, P1, P2) for an APDU */
				if (TxLength == ISO_CMD_SIZE)
				{
					unsigned char cmd[ISO_CMD_SIZE + ISO_LENGTH_SIZE];

					/* copy CLA, INS, P1, P2 */
					memcpy(cmd, TxBuffer, ISO_CMD_SIZE);
					/* set P3 to 0 */
					cmd[ISO_CMD_SIZE] = 0;

					return_value = gemcore_ISO_INPUT_processing(Lun,
						cmd, ISO_CMD_SIZE + ISO_LENGTH_SIZE, RxBuffer,
						RxLength);
				}
				else
				{
					if (TxLength > (ISO_CMD_SIZE + ISO_LENGTH_SIZE))
					{
						/* Check length to see if it is a full APDU or a TPDU */
						ntestlength = TxBuffer[ISO_OFFSET_LENGTH] + ISO_CMD_SIZE
							+ ISO_LENGTH_SIZE;

						if (TxLength == (ntestlength + ISO_LENGTH_SIZE))
						{
							/* TxBuffer holds a proper APDU */
							return_value = gemcore_ISO_EXCHANGE_processing(Lun,
								TxBuffer, TxLength, RxBuffer, RxLength);
							break;
						}
						else
							if (TxLength > (ntestlength + ISO_LENGTH_SIZE))
							{
								/* Data are too long */
								return_value = IFD_COMMUNICATION_ERROR;

								goto clean_up_and_return;
							}

						/* Incoming TPDU */
						return_value = gemcore_ISO_INPUT_processing(Lun,
							TxBuffer, TxLength, RxBuffer, RxLength);
					}
					else
					{
						/* TxBuffer holds too little data to form an APDU+length */
						return_value = IFD_COMMUNICATION_ERROR;

						goto clean_up_and_return;
					}
				}
			}

			break;

		case T_1:
			/* Check if command is going to fit in buffer
			 * cmd byte + TxLength + Le */
			if (CMD_BUF_SIZE < (GC_SIZE_CMD + TxLength + ISO_LENGTH_SIZE))
			{
				/* Buffer too small, send an error */
				return_value = IFD_COMMUNICATION_ERROR;
				goto clean_up_and_return;
			}

			return_value = gemcore_ISO_EXCHANGE_processing(Lun,
				TxBuffer, TxLength, RxBuffer, RxLength);

			break;

		default:
			return_value = IFD_PROTOCOL_NOT_SUPPORTED;
	}

clean_up_and_return:
	if (return_value != IFD_SUCCESS)
		*RxLength = 0;

	return return_value;

} /* IFDHTransmitToICC */


RESPONSECODE IFDHControl(DWORD Lun, DWORD ControlCode, PUCHAR TxBuffer,
	DWORD TxLength, PUCHAR RxBuffer, DWORD RxLength, PDWORD pdwBytesReturned)
{
	/*
	 * This function performs a data exchange with the reader (not the
	 * card) specified by Lun.  Here XXXX will only be used. It is
	 * responsible for abstracting functionality such as PIN pads,
	 * biometrics, LCD panels, etc.  You should follow the MCT, CTBCS
	 * specifications for a list of accepted commands to implement.
	 *
	 * TxBuffer - Transmit data TxLength - Length of this buffer. RxBuffer
	 * - Receive data RxLength - Length of the received data.  This
	 * function will be passed the length of the buffer RxBuffer and it
	 * must set this to the length of the received data.
	 *
	 * Notes: RxLength should be zero on error.
	 */

	DEBUG_INFO2("lun: %lX", Lun);
	(void)ControlCode;
	(void)TxBuffer;
	(void)TxLength;
	(void)RxBuffer;
	(void)RxLength;

	/* this function is unsupported */
	*pdwBytesReturned = 0;

	return IFD_COMMUNICATION_ERROR;
} /* IFDHControl */


RESPONSECODE IFDHICCPresence(DWORD Lun)
{
	/*
	 * This function returns the status of the card inserted in the
	 * reader/slot specified by Lun.  It will return either:
	 *
	 * returns: IFD_ICC_PRESENT IFD_ICC_NOT_PRESENT
	 * IFD_COMMUNICATION_ERROR
	 */

	UCHAR pcbuffer[GC_SIZE_CARD_STATUS];
	RESPONSECODE return_value = IFD_COMMUNICATION_ERROR;
	DWORD length;

	DEBUG_PERIODIC2("lun: %lX", Lun);

	if (iLunCheck(Lun))
		return IFD_COMMUNICATION_ERROR;

	length = sizeof(pcbuffer);
	if (GCCmdCardStatus(Lun, pcbuffer, &length) != IFD_SUCCESS)
	{
		DEBUG_CRITICAL("GCCmdCardStatus failed");
		return_value = IFD_COMMUNICATION_ERROR;
		goto clean_up_and_return;
	}

	if (GC_MASK_ICC_PRESENCE & pcbuffer[GC_OFFSET_STAT_BYTE])
	{
		/* Card is present, but is it powered-up? */
		if (GC_MASK_POWER & pcbuffer[GC_OFFSET_STAT_BYTE])
		{
			DEBUG_PERIODIC("Card present and powered");
			/* Powered, so the ressource manager did not miss a quick
			 * removal/re-insertion */

			return_value = IFD_ICC_PRESENT;
			goto clean_up_and_return;
		}
		else
		{
			/* Card present but not powered up
			 * Check if a power down has been requested */
			if (pgSlots[LunToReaderIndex(Lun)].bPowerFlags & MASK_POWERFLAGS_PDWN)
			{
				DEBUG_PERIODIC("Card present not powered, power down requested");
				/* Powerdown requested, so situation is normal */

				return_value = IFD_ICC_PRESENT;
				goto clean_up_and_return;
			}

			/* Card inserted, not powered on but power down has not been
			 * requested
			 * Has the card been powered up already? */
			if (pgSlots[LunToReaderIndex(Lun)].bPowerFlags & MASK_POWERFLAGS_PUP)
			{
				DEBUG_PERIODIC("Card pull-out+re-insert detected CARD OUT SIMULATION");
				/* Power-up has been requested, but not power down and power is
				 * down.  This should happen only if the card has been pulled
				 * out and reinserted too quickly for the resource manager to
				 * realise. A card out event is therefore simulated. Clear ATR
				 * buffer */
				pgSlots[LunToReaderIndex(Lun)].nATRLength = 0;
				*pgSlots[LunToReaderIndex(Lun)].pcATRBuffer = '\0';
				/* reset power flags */
				pgSlots[LunToReaderIndex(Lun)].bPowerFlags = POWERFLAGS_RAZ;

				return_value = IFD_ICC_NOT_PRESENT;
				goto clean_up_and_return;
			}

			DEBUG_PERIODIC("Card present, just inserted");
			/* If control gets here, the card is in, not powered on, with
			 * no power down request and no previous power up request
			 * it is therefore a card insertion event */
			pgSlots[LunToReaderIndex(Lun)].nATRLength = 0;
			*pgSlots[LunToReaderIndex(Lun)].pcATRBuffer = '\0';
			/* reset power flags */
			pgSlots[LunToReaderIndex(Lun)].bPowerFlags = POWERFLAGS_RAZ;

			return_value = IFD_ICC_PRESENT;
			goto clean_up_and_return;
		}
	}
	else
	{
		DEBUG_PERIODIC("Card absent");
		/* Clear ATR buffer */
		pgSlots[LunToReaderIndex(Lun)].nATRLength = 0;
		*pgSlots[LunToReaderIndex(Lun)].pcATRBuffer = '\0';
		/* Card removed, clear the flags */
		pgSlots[LunToReaderIndex(Lun)].bPowerFlags =
			POWERFLAGS_RAZ;

		return_value = IFD_ICC_NOT_PRESENT;
	}

clean_up_and_return:
	return return_value;
} /* IFDHICCPresence */

void IFDSetEmv(DWORD lun)
{
	pgSlots[LunToReaderIndex(lun)].bPowerFlags |= MASK_POWERFLAGS_EMV;
} /* IFDSetEmv */

