/*
    protocol_t0.c
    Handling of ISO 7816 T=0 protocol

    This file is part of the Unix driver for Towitoko smartcard readers
    Copyright (C) 2000 Carlos Prados <cprados@yahoo.com>

    This version is modified by doz21 to work in a special manner ;)

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "../globals.h"

#ifdef WITH_CARDREADER
#include "../oscam-time.h"
#include "icc_async.h"
#include "protocol_t0.h"
/*
 * Not exported constants definition
 */

#define OK 0
#define ERROR 1

#define PROTOCOL_T0_MAX_NULLS          200
#define PROTOCOL_T0_DEFAULT_WI         10
#define PROTOCOL_T0_MAX_SHORT_COMMAND  260
#define PROTOCOL_T0_MAX_SHORT_RESPONSE 258

#define PROTOCOL_T14_MAX_NULLS          200
#define PROTOCOL_T14_DEFAULT_WI         10
#define PROTOCOL_T14_MAX_SHORT_COMMAND  260
#define PROTOCOL_T14_MAX_SHORT_RESPONSE 258

/* Types of APDU's */
#define APDU_CASE_1 0x0001  /* Nor send neither receive data */
#define APDU_CASE_2S    0x0002  /* Receive data (1..256) */
#define APDU_CASE_3S    0x0003  /* Send data (1..255) */
#define APDU_CASE_4S    0x0004  /* Send data (1..255) and receive data (1..256) */
#define APDU_CASE_2E    0x0102  /* Receive data (1..65536) */
#define APDU_CASE_3E    0x0103  /* Send data (1..65535) */
#define APDU_CASE_4E    0x0104  /* Send data (1..65535) and receive data (1..65536) */
#define APDU_MALFORMED      5   /* Malformed APDU */

/* Timings in ATR are not used in T=0 cards */
/* #undef PROTOCOL_T0_USE_DEFAULT_TIMINGS */

/*
 * Not exported functions declaration
 */

static int32_t Protocol_T0_Case2E(struct s_reader *reader, unsigned char *command, uint16_t command_len, unsigned char *rsp, uint16_t *lr);
static int32_t Protocol_T0_Case3E(struct s_reader *reader, unsigned char *command, unsigned char *rsp, uint16_t *lr);
static int32_t Protocol_T0_Case4E(struct s_reader *reader, unsigned char *command, uint16_t command_len, unsigned char *rsp, uint16_t *lr);
static int32_t Protocol_T0_ExchangeTPDU(struct s_reader *reader, unsigned char *command, uint16_t command_len, unsigned char *rsp, uint16_t *lr);

static int32_t APDU_Cmd_Case(unsigned char *command, uint16_t command_len){
	unsigned char B1;
	uint16_t B2B3;
	uint32_t L;
	int32_t res;

	/* Calculate length of body */
	L = MAX(command_len - 4, 0);

	/* Case 1 */
	if(L == 0)
		{ res = APDU_CASE_1; }
	else
	{
		/* Get first byte of body */
		B1 = command[4];

		if((B1 != 0) && (L == (uint32_t)B1 + 1))
			{ res = APDU_CASE_2S; }
		else if(L == 1)
			{ res = APDU_CASE_3S; }
		else if((B1 != 0) && (L == (uint32_t)B1 + 2))
			{ res = APDU_CASE_4S; }
		else if((B1 == 0) && (L > 2))
		{
			/* Get second and third byte of body */
			B2B3 = (((uint16_t)(command[5]) << 8) | command[6]);

			if((B2B3 != 0) && (L == (uint32_t)B2B3 + 3))
				{ res = APDU_CASE_2E; }
			else if(L == 3)
				{ res = APDU_CASE_3E; }
			else if((B2B3 != 0) && (L == (uint32_t)B2B3 + 5))
				{ res = APDU_CASE_4E; }
			else
				{ res = APDU_MALFORMED; }
		}
		else
			{ res = APDU_MALFORMED; }
	}
	return res;
}

/*
 * Exported funtions definition
 */

int32_t Protocol_T0_Command(struct s_reader *reader, unsigned char *command, uint16_t command_len, unsigned char *rsp, uint16_t *lr)
{
	*lr = 0; //will be returned in case of error
	if(command_len < 5)  //APDU_CASE_1 or malformed
		{ return ERROR; }
	int32_t cmd_case = APDU_Cmd_Case(command, command_len);
	switch(cmd_case)
	{
	case APDU_CASE_2E:
		return Protocol_T0_Case2E(reader, command, command_len, rsp, lr); /* fallthrough */
	case APDU_CASE_3E:
		return Protocol_T0_Case3E(reader, command, rsp, lr); /* fallthrough */
	case APDU_CASE_4E:
		return Protocol_T0_Case4E(reader, command, command_len, rsp, lr); /* fallthrough */
	case APDU_CASE_4S:
		command_len--; /* fallthrough */ //FIXME this should change 4S to 2S/3S command
	case APDU_CASE_2S: /* fallthrough */
	case APDU_CASE_3S:
		return Protocol_T0_ExchangeTPDU(reader, command, command_len, rsp, lr); /* fallthrough */
	default:
		rdr_log_dbg(reader, D_IFD, "Protocol: T=0: Invalid APDU");
		return ERROR;
	}
}

/*
 * Not exported functions definition
 */


static int32_t Protocol_T0_Case2E(struct s_reader *reader, unsigned char *command, uint16_t command_len, unsigned char *rsp, uint16_t *lr)
{
	unsigned char buffer[PROTOCOL_T0_MAX_SHORT_COMMAND];
	unsigned char tpdu_rsp[CTA_RES_LEN];
	uint16_t tpdu_lr = 0;
	uint32_t i;

	uint32_t Lc = (((uint32_t)(command[5]) << 8) | command[6]);
	if(Lc < 256)
	{
		/* MAP APDU onto command TPDU */
		memcpy(buffer, command, 4);
		buffer[4] = (unsigned char) Lc;
		memcpy(buffer + 5, command + 7, buffer[4]);
		return Protocol_T0_ExchangeTPDU(reader, buffer, buffer[4] + 5, rsp, lr);
	}

	/* Prepare envelope TPDU */
	buffer[0] = command[0];
	buffer[1] = 0xC2;
	buffer[2] = 0x00;
	buffer[3] = 0x00;

	for(i = 0; i < command_len; i += buffer[4])
	{
		/* Create envelope command TPDU */
		buffer[4] = MIN(255, command_len - i);
		memcpy(buffer + 5, command + i, buffer[4]);
		call(Protocol_T0_ExchangeTPDU(reader, buffer, buffer[4] + 5, tpdu_rsp, &tpdu_lr));
		/*  Card does support envelope command */
		if(tpdu_rsp[tpdu_lr - 2] == 0x90)
		{
			/* This is not the last segment */
			if(buffer[4] + i < command_len)
				{ tpdu_lr = 0; }
			else
			{
				memcpy(rsp, tpdu_rsp, tpdu_lr); // Map response TPDU onto APDU
				*lr = tpdu_lr;
			}
		}
		else /* Card does not support envelope command or error */
		{
			memcpy(rsp, tpdu_rsp, tpdu_lr); // Map response TPDU onto APDU
			*lr = tpdu_lr;
			break;
		}
	}
	return OK;
}


static int32_t Protocol_T0_Case3E(struct s_reader *reader, unsigned char *command, unsigned char *rsp, uint16_t *lr)
{
	int32_t ret;
	unsigned char buffer[5];
	unsigned char tpdu_rsp[CTA_RES_LEN];
	uint16_t tpdu_lr = 0;
	int32_t Lm, Lx;

	uint32_t Le = ((((uint32_t)(command[5]) << 8) | command[6]) == 0 ? 65536 : (((uint32_t)(command[5]) << 8) | command[6]));
	memcpy(buffer, command, 4);//Map APDU command onto TPDU

	if(Le <= 256)
	{
		buffer[4] = (unsigned char)Le;
		return Protocol_T0_ExchangeTPDU(reader, buffer, 5, rsp, lr); //this was Case3S !!!
	}

	/* Map APDU onto command TPDU */
	buffer[4] = 0x00;
	call(Protocol_T0_ExchangeTPDU(reader, buffer, 5 , tpdu_rsp, &tpdu_lr));

	if(tpdu_rsp[tpdu_lr - 2] == 0x6C)   /* Le not accepted, La indicated */
	{
		/* Map command APDU onto TPDU */
		memcpy(buffer, command, 4);
		buffer[4] = tpdu_rsp[tpdu_lr - 1];

		/* Delete response TPDU */
		tpdu_lr = 0;

		return Protocol_T0_ExchangeTPDU(reader, buffer, 5, rsp, lr); //Reissue command
	}

	memcpy(rsp, tpdu_rsp, tpdu_lr);//Map response TPDU onto APDU without change , also for SW1 = 0x67
	*lr = tpdu_lr;
	ret = OK;
	if(tpdu_rsp[tpdu_lr - 2] == 0x61)   /* Command processed, Lx indicated */
	{
		Lx = (tpdu_rsp[tpdu_lr - 1] == 0x00) ? 256 : tpdu_rsp[tpdu_lr - 1];
		Lm = Le - (*lr - 2);

		/* Prepare Get Response TPDU */
		buffer[0] = command[0];
		buffer[1] = 0xC0;
		buffer[2] = 0x00;
		buffer[3] = 0x00;

		while(Lm > 0)
		{
			buffer[4] = (unsigned char) MIN(Lm, Lx);
			call(Protocol_T0_ExchangeTPDU(reader, buffer, 5, tpdu_rsp, &tpdu_lr));

			/* Append response TPDU to APDU  */
			if((*lr + tpdu_lr) > CTA_RES_LEN)
			{
				rdr_log(reader, "TPDU Append error, new length %i exceeds max length %i", *lr + tpdu_lr, CTA_RES_LEN);
				return ERROR;
			}
			memcpy(rsp + (*lr - 2), tpdu_rsp, tpdu_lr);
			*lr += tpdu_lr;

			/* Delete response TPDU */
			tpdu_lr = 0;

			Lm = Le - (*lr - 2);
		}/* Lm == 0 */
	}
	return ret;
}


static int32_t Protocol_T0_Case4E(struct s_reader *reader, unsigned char *command, uint16_t command_len, unsigned char *rsp, uint16_t *lr)
{
	int32_t ret;
	unsigned char buffer[PROTOCOL_T0_MAX_SHORT_COMMAND];
	unsigned char tpdu_rsp[CTA_RES_LEN];
	memset(tpdu_rsp, 0, sizeof(tpdu_rsp));
	uint16_t tpdu_lr = 0;
	int32_t Le;

	uint32_t Lc = (((uint32_t)(command[5]) << 8) | command[6]);
	/* 4E1 */
	if(Lc < 256)
	{
		/* Map APDU onto command TPDU */
		memcpy(buffer, command, 4);
		buffer[4] = (unsigned char) Lc;
		memcpy(buffer + 5, command, buffer[4]);
		ret = Protocol_T0_ExchangeTPDU(reader, buffer, buffer[4] + 5, tpdu_rsp, &tpdu_lr);
	}
	else /* 4E2 */
		{ ret = Protocol_T0_Case2E(reader, command, command_len, tpdu_rsp, &tpdu_lr); }

	/* 4E1 a) b) and c) */
	if(ret == OK)
	{
		Le = ((((uint32_t)(command[command_len - 2]) << 8) | command[command_len - 1]) == 0 ? 65536 : (((uint32_t)(command[command_len - 2]) << 8) | command[command_len - 1]));
		if(tpdu_lr > 1 && tpdu_rsp[tpdu_lr - 2] == 0x61)
		{
			/* Lm == (Le - APDU_Rsp_RawLen (tpdu_rsp)) == 0 */
			if(tpdu_rsp[tpdu_lr - 1] != 0x00)
				{ Le = MIN(tpdu_rsp[tpdu_lr - 1], Le); }

			/* Delete response TPDU */
			tpdu_lr = 0;

			/* Prepare extended Get Response APDU command */
			buffer[0] = command[0];
			buffer[1] = 0xC0;
			buffer[2] = 0x00;
			buffer[3] = 0x00;
			buffer[4] = 0x00;     /* B1 = 0x00 */
			buffer[5] = (unsigned char)(Le >> 8);   /* B2 = BL-1 */
			buffer[6] = (unsigned char)(Le & 0x00FF);       /* B3 = BL */
			ret = Protocol_T0_Case3E(reader, buffer, rsp, lr);
		}
		else if(tpdu_lr > 1 && (tpdu_rsp[tpdu_lr - 2] & 0xF0) == 0x60)
		{
			/* Map response TPDU onto APDU without change */
			memcpy(rsp, tpdu_rsp, tpdu_lr);
			*lr = tpdu_lr;
		}
		else
		{
			/* Delete response TPDU */
			tpdu_lr = 0;

			/* Prepare extended Get Response APDU command */
			buffer[0] = command[0];
			buffer[1] = 0xC0;
			buffer[2] = 0x00;
			buffer[3] = 0x00;
			buffer[4] = 0x00;     /* B1 = 0x00 */
			buffer[5] = (unsigned char) Le >> 8;  /* B2 = BL-1 */
			buffer[6] = (unsigned char) Le & 0x00FF;      /* B3 = BL */
			ret = Protocol_T0_Case3E(reader, buffer, rsp, lr);
		}
	}
	return ret;
}


static int32_t Protocol_T0_ExchangeTPDU(struct s_reader *reader, unsigned char *command, uint16_t command_len, unsigned char *rsp, uint16_t *lr)
{
	unsigned char buffer[PROTOCOL_T0_MAX_SHORT_RESPONSE];
	unsigned char *data;
	int32_t Lc, Le, sent, recved, expectedlen;
	int32_t nulls, cmd_case;
	int32_t timeout;
	*lr = 0; //in case of error this will be returned

	cmd_case = APDU_Cmd_Case(command, command_len);
	switch(cmd_case)
	{
	case APDU_CASE_2S:
		Lc = command[4];
		Le = 0;
		expectedlen = 1;
		data = command + 5;
		break;
	case APDU_CASE_3S:
		Lc = 0;
		Le = command[4];
		if(!Le)
			{ expectedlen = 2; }
		else
			{ expectedlen = 1 + Le + 2; }
		data = NULL;
		break;
	default:
		rdr_log_dbg(reader, D_TRACE, "ERROR: invalid cmd_case = %i in Protocol_T0_ExchangeTPDU", cmd_case);
		return ERROR;
	}
	timeout = ICC_Async_GetTimings(reader, reader->char_delay);  // we are going to send: char delay timeout
	if(ICC_Async_Transmit(reader, 5, expectedlen, command, 0, timeout) != OK) { return ERROR; }     //Send header bytes

	/* Initialise counters */
	nulls = 0;
	sent = 0;
	recved = 0;

	/*
	* Let's be a bit paranoid with buffer sizes within this loop
	* so it doesn't overflow reception and transmission buffers
	* if card does not strictly respect the protocol
	*/

	while(recved < PROTOCOL_T0_MAX_SHORT_RESPONSE)
	{
		timeout = ICC_Async_GetTimings(reader, reader->read_timeout);  // we are going to receive: WWT timeout
		if(ICC_Async_Receive(reader, 1, buffer + recved, 0, timeout) != OK) { return ERROR; }  //Read one procedure byte

		/* NULL byte received */
		if(buffer[recved] == 0x60)
		{
			nulls++;
			if(nulls >= PROTOCOL_T0_MAX_NULLS)                                  //Maximum number of nulls reached
			{
				rdr_log_dbg(reader, D_TRACE, "ERROR: %s: Maximum number of nulls reached: %d", __func__, nulls);
				return ERROR;
			}
		}
		else if((buffer[recved] & 0xF0) == 0x60 || (buffer[recved] & 0xF0) == 0x90)  /* SW1 byte received */
		{
			rdr_log_dbg(reader, D_TRACE, "SW1: %02X", buffer[recved] & 0xf0);
			recved++;
			if(recved >= PROTOCOL_T0_MAX_SHORT_RESPONSE)
			{
				rdr_log_dbg(reader, D_TRACE, "ERROR: %s: Maximum short response exceeded: %d", __func__, recved);
				return ERROR;
			}
			timeout = ICC_Async_GetTimings(reader, reader->read_timeout);  // we are going to receive: WWT timeout
			if(ICC_Async_Receive(reader, 1, buffer + recved, 0, timeout) != OK) { return ERROR; }   //Read SW2 byte
			rdr_log_dbg(reader, D_TRACE, "SW2: %02X", buffer[recved] & 0xf0);
			recved++;
			break;
		}
		else if((buffer[recved] & 0x0E) == (command[1] & 0x0E))  /* ACK byte received */
		{
			//printf("ack\n");
			/* Reset null's counter */
			nulls = 0;

			/* Case 2 command: send data */
			if(cmd_case == APDU_CASE_2S)
			{
				if(sent >= Lc)
				{
					rdr_log_dbg(reader, D_TRACE, "ERROR: %s: ACK byte: sent=%d exceeds Lc=%d", __func__, sent, Lc);
					return ERROR;
				}
				timeout = ICC_Async_GetTimings(reader, reader->char_delay);  // we are going to send: char delay timeout
				if(ICC_Async_Transmit(reader, MAX(Lc - sent, 0), 2, data + sent, 0, timeout) != OK) { return ERROR; }   /* Send remaining data bytes */
				sent = Lc;
				continue;
			}
			else /* Case 3 command: receive data */
			{
				if(recved > PROTOCOL_T0_MAX_SHORT_RESPONSE)
				{
					rdr_log_dbg(reader, D_TRACE, "ERROR: %s: Case 3 ACK - maximum short response exceeded: %d", __func__, recved);
					return ERROR;
				}

				/*
				* Le <= PROTOCOL_T0_MAX_SHORT_RESPONSE - 2 for short commands
				*/

				/* Read remaining data bytes */
				timeout = ICC_Async_GetTimings(reader, reader->read_timeout);  // we are going to receive: WWT timeout
				if(ICC_Async_Receive(reader, MAX(Le - recved, 0), buffer + recved, 0, timeout) != OK) { return ERROR; }
				recved = Le;
				continue;
			}
		}
		else if((buffer[recved] & 0x0E) == ((~command[1]) & 0x0E))  /* ~ACK byte received */
		{
			//printf("~ack\n");
			nulls = 0;                                                                                              //Reset null's counter

			/* Case 2 command: send data */
			if(cmd_case == APDU_CASE_2S)
			{
				if(sent >= Lc)
				{
					rdr_log_dbg(reader, D_TRACE, "ERROR: %s: ~ACK byte: sent=%d exceeds Lc=%d", __func__, sent, Lc);
					return ERROR;
				}
				timeout = ICC_Async_GetTimings(reader, reader->char_delay);  // we are going to send: char delay timeout
				if(ICC_Async_Transmit(reader, 1, 1, data + sent, 0, timeout) != OK) { return ERROR; }   //Send next data byte
				sent++;
				continue;
			}
			else  /* Case 3 command: receive data */
			{
				if(recved > PROTOCOL_T0_MAX_SHORT_RESPONSE)
				{
					rdr_log_dbg(reader, D_TRACE, "ERROR: %s: Case 3 ~ACK - maximum short response exceeded: %d", __func__, recved);
					return ERROR;
				}
				timeout = ICC_Async_GetTimings(reader, reader->read_timeout);  // we are going to receive: WWT timeout
				if(ICC_Async_Receive(reader, 1, buffer + recved, 0, timeout) != OK) { return ERROR; }   //Read next data byte
				recved++;
				continue;
			}
		}
		else   /* Anything else received */
		{
			rdr_log_dbg(reader, D_TRACE, "ERROR: %s: Received unexpected character: %02X", __func__, buffer[recved]);
			return ERROR;
		}
	}//while

	memcpy(rsp, buffer, recved);
	*lr = recved;
	return OK;
}

int32_t Protocol_T14_ExchangeTPDU(struct s_reader *reader, unsigned char *cmd_raw, uint16_t command_len, unsigned char *rsp, uint16_t *lr)
{
	unsigned char buffer[PROTOCOL_T14_MAX_SHORT_RESPONSE];
	int32_t recved;
	int32_t cmd_case;
	int32_t timeout;
	unsigned char ixor = 0x3E;
	unsigned char ixor1 = 0x3F;
	int32_t i;
	int32_t cmd_len = (int32_t) command_len;
	*lr = 0; //in case of error this is returned

	/* Parse APDU */
	cmd_case = APDU_Cmd_Case(cmd_raw, cmd_len);
	for(i = 0; i < cmd_len; i++)
		{ ixor ^= cmd_raw[i]; }

	/* Check case of command */
	if((cmd_case != APDU_CASE_2S) && (cmd_case != APDU_CASE_3S))
	{
		rdr_log_dbg(reader, D_TRACE, "ERROR: invalid cmd_case = %i in Protocol_T14_ExchangeTPDU", cmd_case);
		return ERROR;
	}

	buffer[0] = 0x01; //send 0x01 byte
	memcpy(buffer + 1, cmd_raw, cmd_len); // apdu
	buffer[cmd_len + 1] = ixor; // xor byte

	/* Send apdu */
	timeout = ICC_Async_GetTimings(reader, reader->char_delay);  // we are going to send: char delay timeout
	if(ICC_Async_Transmit(reader, cmd_len + 2, 0, buffer, 0, timeout) != OK) { return ERROR; }   //send apdu
	if(cmd_raw[0] == 0x02 && cmd_raw[1] == 0x09) { cs_sleepms(2500); }  //FIXME why wait? -> needed for init on overclocked T14 cards

	timeout = ICC_Async_GetTimings(reader, reader->read_timeout);  // we are going to receive: WWT timeout
//	if ((reader->typ == R_SMART) && (reader->smart_type >= 2)) timeout *= 1.1;
	if(ICC_Async_Receive(reader, 8, buffer, 0, timeout) != OK) { return ERROR; }   //Read one procedure byte
	recved = (int32_t)buffer[7];
	if(recved)
	{
		timeout = ICC_Async_GetTimings(reader, reader->read_timeout);  // we are going to receive: WWT timeout
//		if ((reader->typ == R_SMART) && (reader->smart_type >= 2)) timeout *= 1.1;
		if(ICC_Async_Receive(reader, recved, buffer + 8, 0 , timeout) != OK) {return ERROR; }
	}
	timeout = ICC_Async_GetTimings(reader, reader->read_timeout);  // we are going to receive: WWT timeout
//	if ((reader->typ == R_SMART) && (reader->smart_type >= 2)) timeout *= 1.1;
	if(ICC_Async_Receive(reader, 1, &ixor, 0, timeout) != OK) {return ERROR; }
	for(i = 0; i < 8 + recved; i++)
		{ ixor1 ^= buffer[i]; }
	if(ixor1 != ixor)
	{
		rdr_log_dbg(reader, D_TRACE, "ERROR: invalid checksum = %02X expected %02X", ixor1, ixor);
		return ERROR;
	}
	memcpy(buffer + 8 + recved, buffer + 2, 2);
	*lr = recved + 2;
	memcpy(rsp, buffer + 8, *lr);
	return OK;
}
#endif
