/*
 *  $Id: GCUtils.c,v 1.23 2012/04/08 19:09:47 rousseau Exp $
 *  GCUtils.c
 *  ifd-GemPC
 *
 *  Created by giraud on Sat Oct 20 2001.
 *  Updated by Ludovic Rousseau, Oct-Nov 2001
 *  Copyright (c) 2001 Jean-Luc Giraud & Ludovic Rousseau
 *
 *  License: this code is under a double licence COPYING.BSD and COPYING.GPL
 *
 */

#include <stdio.h>
#include <string.h>

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

/* p. 38 ISO Output - Asynchronous Card,
 * p. 58 Read Data From Synchronous Card (ISO Out) */
#define IFD_CMD_ICC_ISO_OUT     0x13

/* p. 39 ISO Input - Asynchronous Card,
 * p. 59 Send Data to Synchronous Card (ISO In) */
#define IFD_CMD_ICC_ISO_IN      0x14

/* p. 40 Exchange APDU - Asynchronous Card,
 * p. 46 Exchange APDU - EMV Compliant,
 * p. 54 Exchange Block - Transparent Mode,
 * p. 60 Exchange with Synchronous Card (ADPU) */
#define IFD_CMD_ICC_APDU        0x15

const UCHAR pcLongDataADPU[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };

/* Check if the Lun is not to large for the pgSlots table
 * returns TRUE in case of error */
int iLunCheck(DWORD Lun)
{
	if (LunToReaderIndex(Lun) >= PCSCLITE_MAX_READERS)
		return TRUE;

	return FALSE;
} /* iLunCheck */

ifd_t gemcore_ISO_OUTPUT_processing(DWORD Lun, PUCHAR TxBuffer,
	DWORD TxLength, PUCHAR RxBuffer, PDWORD RxLength)
{
	UCHAR pccmd_buffer[CMD_BUF_SIZE];
	UCHAR pcbuffer[RESP_BUF_SIZE];
	DWORD nlength,
	 ntemp_length;
	ifd_t return_value = IFD_SUCCESS;	/* Assume it will work */

	DEBUG_INFO("");

	/* Buffer only holds an APDU (no Data),
	 * output command */
	pccmd_buffer[GC_OFFSET_CMD] = IFD_CMD_ICC_ISO_OUT;
	memcpy(pccmd_buffer + GC_OFFSET_APDU, TxBuffer, TxLength);

	/* Send the command */
	nlength = sizeof(pcbuffer);
	if (GCSendCommand(Lun, TxLength + GC_SIZE_CMD, pccmd_buffer,
			&nlength, pcbuffer) != STATUS_SUCCESS)
	{
		DEBUG_CRITICAL("ISO Output failed");
		return_value = IFD_COMMUNICATION_ERROR;
		goto clean_up_and_return;
	}

	/* Copy RxLength to memorize the RxBuffer size */
	ntemp_length = *RxLength;
	return_value = gemcore_status_processing(nlength, RxLength, pcbuffer,
		RxBuffer);

	/* Check if command was successful */
	if (return_value != IFD_SUCCESS)
		goto clean_up_and_return;

	/* Check if there is more data to fetch
	 * First check for 0 length */
	if (TxBuffer[ISO_OFFSET_LENGTH] == 0)
	{
		/* APDU Lenght byte is 0, check if card sent Data (256 Data) */
		if (nlength <= (ISO_SIZE_SW + GC_READER_TRANSMIT_RESP_LENGTH))
			/* Length was 0 and card returned no data: Case 1 command */
			goto clean_up_and_return;

		/* Data are left
		 * set nlength to remaining space avaliable in RxBuffer */
		nlength = ntemp_length - *RxLength;
		if (nlength <= 0)
		{
			return_value = IFD_COMMUNICATION_ERROR;
			goto clean_up_and_return;
		}

		/* Retrieve remaining data and paste it after the first half */
		return_value = gemcore_long_data_OUTPUT_processing(Lun,
			IFD_CMD_ICC_ISO_OUT, nlength, RxLength,
			RxBuffer + *RxLength);
	}
	else
		if ((TxBuffer[ISO_OFFSET_LENGTH] > GC_ISO_OUTPUT_MAX_DATA_LENGTH) &&
			(nlength-ISO_SIZE_SW >= GC_ISO_OUTPUT_MAX_DATA_LENGTH))
		{
			/* Data are left
			 * set nlength to remaining space avaliable in pcbuffer */
			nlength = ntemp_length - *RxLength;

			/* Retrieve remaining data and paste it after the first half */
			return_value = gemcore_long_data_OUTPUT_processing(Lun,
				IFD_CMD_ICC_ISO_OUT, nlength, RxLength,
				RxBuffer + *RxLength);
		}

clean_up_and_return:
	/* Buffers clean-up */
	bzero(pccmd_buffer, sizeof(pccmd_buffer));
	bzero(pcbuffer, sizeof(pcbuffer));

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

	return return_value;
} /* gemcore_ISO_OUTPUT_processing */

ifd_t gemcore_ISO_INPUT_processing(DWORD Lun, PUCHAR TxBuffer,
	DWORD TxLength, PUCHAR RxBuffer, PDWORD RxLength)
{
	UCHAR pccmd_buffer[CMD_BUF_SIZE];
	UCHAR pcbuffer[RESP_BUF_SIZE];
	DWORD nlength;
	ifd_t return_value = IFD_SUCCESS;	/* Assume it will work */

	DEBUG_INFO("");

	nlength = TxBuffer[ISO_OFFSET_LENGTH];
#if 0
	/*
	 * this part of code is disabled since a nlength == 0 si used for Case1
	 * APDUs (CLA INS P1 P2). They are tranfsormed in ifdhandler.c in
	 * ISO_INPUT (CLA INS P1 P2 00) TPDUs
	 */

	if (!nlength)
	{
		/* Data field present but Lc=0, forbidden */
		return_value = IFD_COMMUNICATION_ERROR;
		goto clean_up_and_return;
	}
#endif

	if (TxLength < (nlength + ISO_CMD_SIZE + ISO_LENGTH_SIZE))
	{
		/* Not enough Data in TxBuffer */
		return_value = IFD_COMMUNICATION_ERROR;
		goto clean_up_and_return;
	}

	/* If length exceeds maximum for one exchange, send the last part first */
	if (nlength > GC_ISO_INPUT_MAX_DATA_LENGTH)
	{
		return_value = gemcore_long_data_INPUT_processing(Lun,
			IFD_CMD_ICC_ISO_IN,
			nlength - GC_ISO_INPUT_MAX_DATA_LENGTH,
			TxBuffer + ISO_CMD_SIZE + ISO_LENGTH_SIZE +
			GC_ISO_INPUT_MAX_DATA_LENGTH);

		if (return_value != IFD_SUCCESS)
			goto clean_up_and_return;

		/* Length to send is now smaller */
		TxLength -= nlength - GC_ISO_INPUT_MAX_DATA_LENGTH;
	}

	nlength = sizeof(pcbuffer);
	pccmd_buffer[GC_OFFSET_CMD] = IFD_CMD_ICC_ISO_IN;
	memcpy(pccmd_buffer + GC_OFFSET_APDU, TxBuffer, TxLength);

	if (GCSendCommand(Lun, TxLength + GC_SIZE_CMD,
			pccmd_buffer, &nlength, pcbuffer) != STATUS_SUCCESS)
	{
		DEBUG_CRITICAL("ISO Input failed");
		return_value = IFD_COMMUNICATION_ERROR;
		goto clean_up_and_return;
	}

	return_value = gemcore_status_processing(nlength, RxLength, pcbuffer,
		RxBuffer);

clean_up_and_return:
	/* Buffers clean-up */
	bzero(pccmd_buffer, sizeof(pccmd_buffer));
	bzero(pcbuffer, sizeof(pcbuffer));

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

	return return_value;
} /* gemcore_ISO_INPUT_processing */

ifd_t gemcore_ISO_EXCHANGE_processing(DWORD Lun, PUCHAR TxBuffer,
	DWORD TxLength, PUCHAR RxBuffer, PDWORD RxLength)
{
	UCHAR pccmd_buffer[CMD_BUF_SIZE];
	UCHAR pcbuffer[RESP_BUF_SIZE];
	DWORD nlength;
	ifd_t return_value = IFD_SUCCESS;	/* Assume it will work */

	DEBUG_INFO("");

	/* If length exceeds maximum for one exchange, send the last part first */
	if (TxLength > GC_ISO_EXC_CMD_MAX_APDU_LENGTH)
	{
		return_value = gemcore_long_data_INPUT_processing(Lun,
			IFD_CMD_ICC_APDU,
			TxLength - GC_ISO_EXC_CMD_MAX_APDU_LENGTH,
			TxBuffer + GC_ISO_EXC_CMD_MAX_APDU_LENGTH);

		if (return_value != IFD_SUCCESS)
			goto clean_up_and_return;

		/* Length to send is now smaller */
		TxLength = GC_ISO_EXC_CMD_MAX_APDU_LENGTH;
	}

	/* Build the commmand */
	pccmd_buffer[GC_OFFSET_CMD] = IFD_CMD_ICC_APDU;
	memcpy(pccmd_buffer + GC_OFFSET_APDU, TxBuffer, TxLength);

	/* Add length of expected data */
	pccmd_buffer[GC_OFFSET_APDU + TxLength] = *RxLength;

	/* Send the command */
	nlength = sizeof(pcbuffer);
	if (GCSendCommand(Lun, TxLength + GC_SIZE_CMD, pccmd_buffer,
			&nlength, pcbuffer) != STATUS_SUCCESS)
	{
		DEBUG_CRITICAL("ISO Exchange failed");
		return_value = IFD_COMMUNICATION_ERROR;
		goto clean_up_and_return;
	}

	if (nlength < GC_SIZE_STATUS)
	{
		/* Length should at least be GC_SIZE_STATUS (reader status) */
		return IFD_COMMUNICATION_ERROR;
	}

	if (pcbuffer[GC_STATUS_OFFSET] == GCORE_MORE_DATA)
	{

		if (nlength != (GC_ISO_EXC_RESP_MAX_APDU_LENGTH + GC_SIZE_STATUS))
		{
			/* Should have received a full block */
			return_value = IFD_COMMUNICATION_ERROR;
			goto clean_up_and_return;
		}

		/* There are extra data to fetch on the reader
		 * store what was receive */
		if (*RxLength <= (GC_ISO_EXC_RESP_MAX_APDU_LENGTH))
		{
			memcpy(RxBuffer, pcbuffer + GC_OFFSET_RESP_DATA, *RxLength);
			/* No space left in buffer, return directly */
			goto clean_up_and_return;
		}

		memcpy(RxBuffer, pcbuffer + GC_OFFSET_RESP_DATA,
			GC_ISO_EXC_RESP_MAX_APDU_LENGTH);

		/* set nlength to remaining space avaliable in RxBuffer */
		nlength = *RxLength - GC_ISO_EXC_RESP_MAX_APDU_LENGTH;

		/* set RxLength to what was already received */
		*RxLength = GC_ISO_EXC_RESP_MAX_APDU_LENGTH;

		/* Retrieve remaining data and paste it after the first half */
		return_value =
			gemcore_long_data_OUTPUT_processing(Lun,
			IFD_CMD_ICC_APDU, nlength, RxLength,
			RxBuffer + *RxLength);
	}
	else
		return_value = gemcore_status_processing(nlength, RxLength,
			pcbuffer, RxBuffer);

clean_up_and_return:
	/* Buffers clean-up */
	bzero(pccmd_buffer, sizeof(pccmd_buffer));
	bzero(pcbuffer, sizeof(pcbuffer));

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

	return return_value;
} /* gemcore_ISO_EXCHANGE_processing */

ifd_t gemcore_status_processing(DWORD nlength, PDWORD RxLength,
	PUCHAR pcbuffer, PUCHAR RxBuffer)
{
	DEBUG_INFO("");

	if (nlength < GC_SIZE_STATUS)
		/* Length should at least be GC_SIZE_STATUS (reader status) */
		return IFD_COMMUNICATION_ERROR;

	nlength -= GC_SIZE_STATUS;
	switch (GCGemCoreError(pcbuffer[GC_STATUS_OFFSET], __FILE__, __LINE__,
		__FUNCTION__))
	{
		case GCORE_OK:
		case GCORE_NOT_9000:
		case GCORE_CARD_EXC_INT:	/* SW=6700 */
			/* if ( nlength < pcbuffer[OFFSET_LNG] ) */
			/* WHAT SHOULD WE DO */
			*RxLength = (*RxLength < nlength) ? (*RxLength) : nlength;
			memcpy(RxBuffer, pcbuffer + GC_OFFSET_RESP_DATA, *RxLength);
			break;

		case GCORE_CARD_MUTE:
			return IFD_RESPONSE_TIMEOUT;

		case GCORE_CARD_PROT_ERR:
			return IFD_PROTOCOL_NOT_SUPPORTED;

		case GCORE_CARD_MISSING:
			return IFD_ICC_NOT_PRESENT;

		default:
			/* There was a problem in sending the command */
			return IFD_COMMUNICATION_ERROR;
	}
	return IFD_SUCCESS;
} /* gemcore_status_processing */

ifd_t gemcore_long_data_INPUT_processing(DWORD Lun, UCHAR cCMD,
	DWORD ndatalength, PUCHAR pcbuffer)
{
	UCHAR pccmd_buffer[CMD_BUF_SIZE];
	UCHAR pcresp_buffer[RESP_BUF_SIZE];
	UCHAR pcresp_buffer2[RESP_BUF_SIZE];
	DWORD nlength, nlength2;
	ifd_t return_value;

	DEBUG_INFO("");

	/* command */
	pccmd_buffer[GC_OFFSET_CMD] = cCMD;

	/* use specific APDU */
	memcpy(pccmd_buffer + GC_OFFSET_APDU, pcLongDataADPU,
		sizeof(pcLongDataADPU));

	/* modify the length byte */
	pccmd_buffer[GC_OFFSET_LENGTH] = (UCHAR) ndatalength;

	/* Check the space left in pccmd_buffer */
	if (sizeof(pccmd_buffer) < (GC_OFFSET_TPDU_CMD_DATA + ndatalength))
	{
		return_value = IFD_COMMUNICATION_ERROR;
		goto clean_up_and_return;
	}

	/* Add the Data (last data bytes of the command */
	memcpy(pccmd_buffer + GC_OFFSET_TPDU_CMD_DATA, pcbuffer, ndatalength);

	/* Send the command */
	nlength = sizeof(pcresp_buffer);

	if (GCSendCommand(Lun,
			sizeof(pcLongDataADPU) + GC_SIZE_CMD + ndatalength,
			pccmd_buffer, &nlength, pcresp_buffer) != STATUS_SUCCESS)
	{
		DEBUG_CRITICAL("ISO Input failed");
		return_value = IFD_COMMUNICATION_ERROR;
		goto clean_up_and_return;
	}

	nlength2 = sizeof(pcresp_buffer2);
	return_value = gemcore_status_processing(nlength, &nlength2,
		pcresp_buffer, pcresp_buffer2);

clean_up_and_return:
	bzero(pccmd_buffer, sizeof(pccmd_buffer));
	bzero(pcresp_buffer, sizeof(pcresp_buffer));
	bzero(pcresp_buffer2, sizeof(pcresp_buffer2));

	return return_value;
} /* gemcore_long_data_INPUT_processing */

/*
 * nbuf_size should hold the max length available in pcbuffer
 */
ifd_t gemcore_long_data_OUTPUT_processing(DWORD Lun, UCHAR cCMD,
	DWORD nbuf_size, PDWORD RxLength, PUCHAR pcbuffer)
{
	UCHAR pccmd_buffer[CMD_BUF_SIZE];
	UCHAR pcresp_buffer[RESP_BUF_SIZE];
	DWORD nlength;
	ifd_t return_value;

	DEBUG_INFO("");

	/* command */
	pccmd_buffer[GC_OFFSET_CMD] = cCMD;

	/* use specific APDU */
	memcpy(pccmd_buffer + GC_OFFSET_APDU, pcLongDataADPU,
		sizeof(pcLongDataADPU));

	/* Send the command */
	nlength = sizeof(pcresp_buffer);
	if (GCSendCommand(Lun, sizeof(pcLongDataADPU) + GC_SIZE_CMD,
			pccmd_buffer, &nlength, pcresp_buffer) != STATUS_SUCCESS)
	{
		DEBUG_CRITICAL("ISO Output failed");
		bzero(pccmd_buffer, sizeof(pccmd_buffer));
		bzero(pcresp_buffer, sizeof(pcresp_buffer));
		return IFD_COMMUNICATION_ERROR;
	}

	return_value = gemcore_status_processing(nlength, &nbuf_size,
		pcresp_buffer, pcbuffer);
	*RxLength = *RxLength + nbuf_size;

	bzero(pccmd_buffer, sizeof(pccmd_buffer));
	bzero(pcresp_buffer, sizeof(pcresp_buffer));

	return return_value;
} /* gemcore_long_data_OUTPUT_processing */

