/* ----------------------------------------------------------------------------
 *         ATMEL Microcontroller Software Support 
 * ----------------------------------------------------------------------------
 * Copyright (c) 2008, Atmel Corporation
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the disclaimer below.
 *
 * Atmel's name may not be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ----------------------------------------------------------------------------
 */

//------------------------------------------------------------------------------
/// \unit
///
/// !Purpose
/// 
/// CCID driver
/// 
/// !Usage
/// 
/// Explanation on the usage of the code made available through the header file.
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
//       Headers
//------------------------------------------------------------------------------

#include "board.h"
#include "simtrace.h"

#ifdef HAVE_CCID

#include <USBDDriver.h>
#include <USBRequests.h>
#include <USBDescriptors.h>
//#include <usb/device/dfu/dfu.h>
#include <cciddriverdescriptors.h>

// FIXME: Remove DFU related stuff 
/* no DFU bootloader is being used */
#define DFU_NUM_IF      0
#define DFU_IF_DESCRIPTORS_STRUCT
#define DFU_IF_DESCRIPTORS

#define DFU_NUM_STRINGS 0
#define DFU_STRING_DESCRIPTORS

//------------------------------------------------------------------------------
//         Local definition
//------------------------------------------------------------------------------

/// Constants: IDs: Device product ID.
//#define CCIDDriverDescriptors_PRODUCTID       0x6129
#define CCIDDriverDescriptors_PRODUCTID       SIMTRACE_PRODUCT_ID
/// Constants: IDs: Device vendor ID.
#define CCIDDriverDescriptors_VENDORID        ATMEL_VENDOR_ID
//#define CCIDDriverDescriptors_VENDORID        0x03EB
/// Constants: IDs: Device release number.
#define CCIDDriverDescriptors_RELEASE         0x0100

//------------------------------------------------------------------------------
//         Types
//------------------------------------------------------------------------------

/// Driver structure for an CCID device
typedef struct {

	/// CCID message
	S_ccid_bulk_in_header  sCcidMessage;
	/// CCID command
	S_ccid_bulk_out_header sCcidCommand;
	/// Interrupt message answer
	unsigned char          BufferINT[4];
	/// Buffer data of message
	unsigned char          ProtocolDataStructure[10];
	/// Protocol used
	unsigned char          bProtocol;
	/// SlotStatus
	/// Bit 0 = Slot 0 current state
	/// Bit 1 = Slot 0 changed status
	/// Bit 2 = Slot 1 current state
	/// Bit 3 = Slot 1 changed status
	/// Bit 4 = Slot 2 current state
	/// Bit 5 = Slot 2 changed status
	unsigned char          SlotStatus;

} CCIDDriver;

//------------------------------------------------------------------------------
//         Local variables
//------------------------------------------------------------------------------

/// Static instance of the CCID device driver.
static CCIDDriver ccidDriver;
static CCIDDriverConfigurationDescriptors *configurationDescriptorsFS;

//------------------------------------------------------------------------------
//      Internal functions
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
/// Initializes the CCID device driver.
//------------------------------------------------------------------------------
void CCIDDriver_Initialize( void )
{
	configurationDescriptorsFS = (CCIDDriverConfigurationDescriptors *) configurationDescriptorsArr[CFG_NUM_CCID-1];
}

//------------------------------------------------------------------------------
/// Response Pipe, Bulk-IN Messages
/// Return the Slot Status to the host
/// Answer to:
///   PC_to_RDR_IccPowerOff
///   PC_to_RDR_GetSlotStatus
///   PC_to_RDR_IccClock
///   PC_to_RDR_T0APDU
///   PC_to_RDR_Mechanical
///   PC_to_RDR_Abort and Class specific ABORT request
//------------------------------------------------------------------------------
static void RDRtoPCSlotStatus( void )
{
	// Header fields settings
	ccidDriver.sCcidMessage.bMessageType = RDR_TO_PC_SLOTSTATUS;
	ccidDriver.sCcidMessage.wLength   = 0;

	if (ccidDriver.SlotStatus == ICC_INSERTED_EVENT) {
		ccidDriver.sCcidMessage.bStatus = 0;    /* ICC present and active card */
	} else if (ccidDriver.SlotStatus == ICC_NOT_PRESENT) {
		ccidDriver.sCcidMessage.bStatus = 2;    /* No ICC present*/
	} else{
		TRACE_ERROR("Strange bStatus");
		ccidDriver.sCcidMessage.bStatus = 0;
	}
	ccidDriver.sCcidMessage.bError    = 0;
	// 00h Clock running
	// 01h Clock stopped in state L
	// 02h Clock stopped in state H
	// 03h Clock stopped in an unknown state
	// All other values are Reserved for Future Use.
	ccidDriver.sCcidMessage.bSpecific = 0;
}

//------------------------------------------------------------------------------
/// Response Pipe, Bulk-IN Messages
/// Answer to PC_to_RDR_IccPowerOn
//------------------------------------------------------------------------------
static void RDRtoPCDatablock_ATR( void )
{
	unsigned char i;
	unsigned char Atr[ATR_SIZE_MAX];
	unsigned char length;
	uint32_t status; 

	TRACE_DEBUG(".");

	status = ISO7816_Datablock_ATR( Atr, &length );
	ISO7816_Decode_ATR( Atr );

	if (status == 0) {
		TRACE_DEBUG("Timeout occured while reading ATR");
// FIXME: react properly to timeout..
//        return;
	}

// FIXME: More tests? Is bProtocol = Atr[3] ?
	if( length > 5 ) {
		ccidDriver.ProtocolDataStructure[1] = Atr[3]&0x0F; // TD(1)
		ccidDriver.bProtocol = Atr[3]&0x0F;           // TD(1)
		TRACE_INFO("Protocol data structure: 0x%x\n\r",
					    ccidDriver.ProtocolDataStructure[1]);
	}

	// S_ccid_protocol_t0
	// bmFindexDindex
	ccidDriver.ProtocolDataStructure[0] = Atr[2];     // TA(1)

	// bmTCCKST0
	// For T=0 ,B0 - 0b, B7-2 - 000000b
	// B1 - Convention used (b1=0 for direct, b1=1 for inverse)

	// bGuardTimeT0
	// Extra Guardtime between two characters. Add 0 to 254 etu to the normal 
	// guardtime of 12etu. FFh is the same as 00h.
	ccidDriver.ProtocolDataStructure[2] = Atr[4];     // TC(1)
	// AT91C_BASE_US0->US_TTGR = 0;  // TC1

	// bWaitingIntegerT0
	// WI for T=0 used to define WWT
	ccidDriver.ProtocolDataStructure[3] = Atr[7];     // TC(2)

	// bClockStop
	// ICC Clock Stop Support
	// 00 = Stopping the Clock is not allowed
	// 01 = Stop with Clock signal Low
	// 02 = Stop with Clock signal High
	// 03 = Stop with Clock either High or Low
	ccidDriver.ProtocolDataStructure[4] = 0x00;       // 0 to 3

	// Header fields settings
	ccidDriver.sCcidMessage.bMessageType = RDR_TO_PC_DATABLOCK;
	ccidDriver.sCcidMessage.wLength      = length;  // Size of ATR
	ccidDriver.sCcidMessage.bSizeToSend += length;  // Size of ATR
	// bChainParameter: 00 the response APDU begins and ends in this command
	ccidDriver.sCcidMessage.bSpecific    = 0;

	for( i=0; i<length; i++ ) {

		ccidDriver.sCcidMessage.abData[i]  = Atr[i];
	}

	// Set the slot to an active status
	ccidDriver.sCcidMessage.bStatus = 0;
	ccidDriver.sCcidMessage.bError = 0;
}

//------------------------------------------------------------------------------
/// Response Pipe, Bulk-IN Messages
/// In other cases, the response message has the following format: 
/// The response data will contain the optional data returned by the ICC, 
/// followed by the 2 byte-size status words SW1-SW2.
///
/// Answer to:
///   PC_to_RDR_XfrBlock
///   PC_to_RDR_Secure
//------------------------------------------------------------------------------
static void RDRtoPCDatablock( void )
{
	//TRACE_DEBUG(".");

	// Header fields settings
	ccidDriver.sCcidMessage.bMessageType = RDR_TO_PC_DATABLOCK;
	ccidDriver.sCcidMessage.bSizeToSend += ccidDriver.sCcidMessage.wLength;
	// bChainParameter: 00 the response APDU begins and ends in this command
	ccidDriver.sCcidMessage.bSpecific = 0;

	// Set the slot to an active status
	ccidDriver.sCcidMessage.bStatus = 0;
	ccidDriver.sCcidMessage.bError = 0;
}

//------------------------------------------------------------------------------
/// Response Pipe, Bulk-IN Messages
/// Answer to:
///   PC_to_RDR_GetParameters
///   PC_to_RDR_ResetParameters
///   PC_to_RDR_SetParameters
//------------------------------------------------------------------------------
static void RDRtoPCParameters( void )
{
	unsigned int i;

	TRACE_DEBUG(".");

	// Header fields settings
	ccidDriver.sCcidMessage.bMessageType = RDR_TO_PC_PARAMETERS;

	//ccidDriver.sCcidMessage.bStatus = 0;
	ccidDriver.sCcidMessage.bError  = 0;

	if( ccidDriver.ProtocolDataStructure[1] == PROTOCOL_TO ) {

		// T=0
		ccidDriver.sCcidMessage.wLength   = sizeof(S_ccid_protocol_t0);
		ccidDriver.sCcidMessage.bSpecific = PROTOCOL_TO;
	}
	else {

		// T=1
		ccidDriver.sCcidMessage.wLength   = sizeof(S_ccid_protocol_t1);
		ccidDriver.sCcidMessage.bSpecific = PROTOCOL_T1;
	}

	ccidDriver.sCcidMessage.bSizeToSend += ccidDriver.sCcidMessage.wLength;

	for( i=0; i<ccidDriver.sCcidMessage.wLength; i++ ) {
		ccidDriver.sCcidMessage.abData[i] = ccidDriver.ProtocolDataStructure[i];
	}

}

//------------------------------------------------------------------------------
/// Response Pipe, Bulk-IN Messages
/// Answer to:
///   PC_to_RDR_Escape
//------------------------------------------------------------------------------
static void RDRtoPCEscape( unsigned char length, unsigned char *data_send_from_CCID )
{
	unsigned int i;

	TRACE_DEBUG(".");

	// Header fields settings
	ccidDriver.sCcidMessage.bMessageType = RDR_TO_PC_ESCAPE;

	ccidDriver.sCcidMessage.wLength   = length;

	ccidDriver.sCcidMessage.bStatus = 0;
	ccidDriver.sCcidMessage.bError  = 0;

	ccidDriver.sCcidMessage.bSpecific = 0;  // bRFU

	for( i=0; i<length; i++ ) {
		ccidDriver.sCcidMessage.abData[i] = data_send_from_CCID[i];
	}
}

//------------------------------------------------------------------------------
/// Response Pipe, Bulk-IN Messages
/// Answer to: 
///   PC_to_RDR_SetDataRateAndClockFrequency
//------------------------------------------------------------------------------
static void RDRtoPCDataRateAndClockFrequency( unsigned int dwClockFrequency, 
					                   unsigned int dwDataRate )
{
	TRACE_DEBUG(".");

	// Header fields settings
	ccidDriver.sCcidMessage.bMessageType = RDR_TO_PC_DATARATEANDCLOCKFREQUENCY;

	ccidDriver.sCcidMessage.wLength   = 8;

	ccidDriver.sCcidMessage.bStatus = 0;
	ccidDriver.sCcidMessage.bError  = 0;

	ccidDriver.sCcidMessage.bSpecific = 0;  // bRFU

	ccidDriver.sCcidMessage.abData[0] = dwClockFrequency;
	
	ccidDriver.sCcidMessage.abData[4] = dwDataRate;
}

//------------------------------------------------------------------------------
/// Command Pipe, Bulk-OUT Messages
/// Power On Command - Cold Reset & Warm Reset 
/// Return the ATR to the host
//------------------------------------------------------------------------------
static void PCtoRDRIccPowerOn( void )
{
	TRACE_DEBUG(".");
	if( CCID_FEATURES_AUTO_VOLT == (configurationDescriptorsFS->ccid.dwFeatures & CCID_FEATURES_AUTO_VOLT) ) {

		//bPowerSelect = ccidDriver.sCcidCommand.bSpecific_0;
		ccidDriver.sCcidCommand.bSpecific_0 = VOLTS_AUTO;
	}

	ISO7816_warm_reset();
//    ISO7816_cold_reset();

	// for emulation only //JCB 
	if ( ccidDriver.sCcidCommand.bSpecific_0 != VOLTS_5_0 ) {

		TRACE_ERROR("POWER_NOT_SUPPORTED\n\r");
	}

	else {

		RDRtoPCDatablock_ATR();

	}
}

//------------------------------------------------------------------------------
/// Command Pipe, Bulk-OUT Messages
/// Power Off Command - Set the ICC in an inactive state
/// Return the slot status to the host
//------------------------------------------------------------------------------
static void PCtoRDRIccPowerOff( void )
{
	unsigned char bStatus;

	TRACE_DEBUG(".");

	ISO7816_IccPowerOff();

	//JCB stub
	bStatus = ICC_BS_PRESENT_NOTACTIVATED;

	// Set the slot to an inactive status
	ccidDriver.sCcidMessage.bStatus = 0;
	ccidDriver.sCcidMessage.bError = 0;

	// if error, see Table 6.1-2 errors

	// Return the slot status to the host
	RDRtoPCSlotStatus();
}

//------------------------------------------------------------------------------
/// Command Pipe, Bulk-OUT Messages
/// Get slot status
//------------------------------------------------------------------------------
static void PCtoRDRGetSlotStatus( void )
{
	TRACE_DEBUG(".");

	ccidDriver.sCcidMessage.bStatus = 0;
	ccidDriver.sCcidMessage.bError = 0;

	// Return the slot status to the host
	RDRtoPCSlotStatus();
}

//------------------------------------------------------------------------------
/// Command Pipe, Bulk-OUT Messages
/// If the command header is valid, an APDU command is received and can be read
/// by the application
//------------------------------------------------------------------------------
static void PCtoRDRXfrBlock( void )
{
	uint16_t msglen = 0;
	uint32_t ret;

	TRACE_DEBUG("PCtoRDRXfrBlock\n\r");

	// Check the block length
	if ( ccidDriver.sCcidCommand.wLength > (configurationDescriptorsFS->ccid.dwMaxCCIDMessageLength-10) ) {
		TRACE_DEBUG("Err block/msg len");
		ccidDriver.sCcidMessage.bStatus = 1;
		ccidDriver.sCcidMessage.bError  = 0;
	}
	// check bBWI
	else if ( 0 != ccidDriver.sCcidCommand.bSpecific_0 ) {

		 TRACE_ERROR("Bad bBWI\n\r");
	}
	else {

		// APDU or TPDU
		switch(configurationDescriptorsFS->ccid.dwFeatures 
			  & (CCID_FEATURES_EXC_TPDU|CCID_FEATURES_EXC_SAPDU|CCID_FEATURES_EXC_APDU)) {

			case CCID_FEATURES_EXC_TPDU:
				if (ccidDriver.ProtocolDataStructure[1] == PROTOCOL_TO) {
					TRACE_DEBUG("APDU cmd: %x %x %x ..", ccidDriver.sCcidCommand.APDU[0], ccidDriver.sCcidCommand.APDU[1],ccidDriver.sCcidCommand.APDU[2] );

					// Send commande APDU
					ret = ISO7816_XfrBlockTPDU_T0( ccidDriver.sCcidCommand.APDU ,
					                        ccidDriver.sCcidMessage.abData,
					                        ccidDriver.sCcidCommand.wLength,
					                        &msglen );
					if (ret != 0) {
					    TRACE_ERROR("APDU could not be sent: (US_CSR = 0x%x)", ret);
					    return;
					}
				}
				else {
					if (ccidDriver.ProtocolDataStructure[1] == PROTOCOL_T1) {
					    TRACE_DEBUG("Not supported T=1\n\r");
					}
					else {
					    TRACE_DEBUG("Not supported 0x%x\n\r", ccidDriver.ProtocolDataStructure[1]);
					}
				}
				break;

			case CCID_FEATURES_EXC_APDU:
				TRACE_DEBUG("Not supported CCID_FEATURES_EXC_APDU\n\r");
				break;

			default:
				break;
		}

	}

	ccidDriver.sCcidMessage.wLength = msglen;
	TRACE_DEBUG("USB: 0x%X, 0x%X, 0x%X, 0x%X, 0x%X\n\r", ccidDriver.sCcidMessage.abData[0], 
					                                                ccidDriver.sCcidMessage.abData[1], 
					                                                ccidDriver.sCcidMessage.abData[2], 
					                                                ccidDriver.sCcidMessage.abData[3],
					                                                ccidDriver.sCcidMessage.abData[4] );
	 RDRtoPCDatablock();

}

//------------------------------------------------------------------------------
/// Command Pipe, Bulk-OUT Messages
/// return parameters by the command: RDR_to_PC_Parameters
//------------------------------------------------------------------------------
static void PCtoRDRGetParameters( void )
{
	TRACE_DEBUG(".");

	// We support only one slot

	// bmIccStatus
	if( ISO7816_StatusReset() ) {
		// 0: An ICC is present and active (power is on and stable, RST is inactive
		ccidDriver.sCcidMessage.bStatus = 0;
	}
	else {
		// 1: An ICC is present and inactive (not activated or shut down by hardware error)
		ccidDriver.sCcidMessage.bStatus = 1;
	}

	RDRtoPCParameters();
}

//------------------------------------------------------------------------------
/// Command Pipe, Bulk-OUT Messages
/// This command resets the slot parameters to their default values
//------------------------------------------------------------------------------
static void PCtoRDRResetParameters( void )
{
	TRACE_DEBUG(".");

	ccidDriver.SlotStatus = ICC_NOT_PRESENT;
	ccidDriver.sCcidMessage.bStatus = ccidDriver.SlotStatus;

	RDRtoPCParameters();
}

//------------------------------------------------------------------------------
/// Command Pipe, Bulk-OUT Messages
/// This command is used to change the parameters for a given slot.
//------------------------------------------------------------------------------
static void PCtoRDRSetParameters( void )
{
	TRACE_DEBUG(".");

	ccidDriver.SlotStatus = ccidDriver.sCcidCommand.bSlot;
	ccidDriver.sCcidMessage.bStatus = ccidDriver.SlotStatus;
	// Not all feature supported

	RDRtoPCParameters();
}

//------------------------------------------------------------------------------
/// Command Pipe, Bulk-OUT Messages
/// This command allows the CCID manufacturer to define and access extended 
/// features. 
/// Information sent via this command is processed by the CCID control logic.
//------------------------------------------------------------------------------
static void PCtoRDREscape( void )
{
	TRACE_DEBUG(".");

	// If needed by the user
	ISO7816_Escape();

	// stub, return all value send
	RDRtoPCEscape( ccidDriver.sCcidCommand.wLength, ccidDriver.sCcidCommand.APDU);    
}

//------------------------------------------------------------------------------
/// Command Pipe, Bulk-OUT Messages
/// This command stops or restarts the clock.
//------------------------------------------------------------------------------
static void PCtoRDRICCClock( void )
{
	TRACE_DEBUG(".");

	if( 0 == ccidDriver.sCcidCommand.bSpecific_0 ) {
		// restarts the clock
		ISO7816_RestartClock();
	}
	else {
		// stop clock in the state shown in the bClockStop field
		ISO7816_StopClock();
	}

	RDRtoPCSlotStatus( );    
}

//------------------------------------------------------------------------------
/// Command Pipe, Bulk-OUT Messages
/// This command changes the parameters used to perform the transportation of 
/// APDU messages by the T=0 protocol. 
//------------------------------------------------------------------------------
static void PCtoRDRtoAPDU( void )
{
	unsigned char bmChanges;
	unsigned char bClassGetResponse;
	unsigned char bClassEnvelope;

	TRACE_INFO(".");

	if( configurationDescriptorsFS->ccid.dwFeatures == (CCID_FEATURES_EXC_SAPDU|CCID_FEATURES_EXC_APDU) ) {

		bmChanges = ccidDriver.sCcidCommand.bSpecific_0;
		bClassGetResponse = ccidDriver.sCcidCommand.bSpecific_1;
		bClassEnvelope = ccidDriver.sCcidCommand.bSpecific_2;

		ISO7816_toAPDU();
	}

	RDRtoPCSlotStatus();    
}

//------------------------------------------------------------------------------
/// Command Pipe, Bulk-OUT Messages
/// This is a command message to allow entering the PIN for verification or
/// modification.
//------------------------------------------------------------------------------
static void PCtoRDRSecure( void )
{
	TRACE_DEBUG(".");

	TRACE_DEBUG("For user\n\r");
}

//------------------------------------------------------------------------------
/// Command Pipe, Bulk-OUT Messages
/// This command is used to manage motorized type CCID functionality. 
/// The Lock Card function is used to hold the ICC. 
/// This prevents an ICC from being easily removed from the CCID. 
/// The Unlock Card function is used to remove the hold initiated by the Lock 
/// Card function
//------------------------------------------------------------------------------
static void PCtoRDRMechanical( void )
{
	TRACE_DEBUG(".");
	TRACE_DEBUG("Not implemented\n\r");

	RDRtoPCSlotStatus();
}

//------------------------------------------------------------------------------
/// Command Pipe, Bulk-OUT Messages
/// This command is used with the Control pipe Abort request to tell the CCID 
/// to stop any current transfer at the specified slot and return to a state 
/// where the slot is ready to accept a new command pipe Bulk-OUT message.
//------------------------------------------------------------------------------
static void PCtoRDRAbort( void )
{
	TRACE_DEBUG(".");

	RDRtoPCSlotStatus();
}

//------------------------------------------------------------------------------
/// Command Pipe, Bulk-OUT Messages
/// This command is used to manually set the data rate and clock frequency of 
/// a specific slot.
//------------------------------------------------------------------------------
static void PCtoRDRSetDataRateAndClockFrequency( void )
{
	unsigned int dwClockFrequency;
	unsigned int dwDataRate;

	TRACE_DEBUG(".");

	dwClockFrequency = ccidDriver.sCcidCommand.APDU[0]
					 + (ccidDriver.sCcidCommand.APDU[1]<<8)
					 + (ccidDriver.sCcidCommand.APDU[2]<<16)
					 + (ccidDriver.sCcidCommand.APDU[3]<<24);

	dwDataRate = ccidDriver.sCcidCommand.APDU[4]
			   + (ccidDriver.sCcidCommand.APDU[5]<<8)
			   + (ccidDriver.sCcidCommand.APDU[6]<<16)
			   + (ccidDriver.sCcidCommand.APDU[7]<<24);

	ISO7816_SetDataRateandClockFrequency( dwClockFrequency, dwDataRate );

	RDRtoPCDataRateAndClockFrequency( dwClockFrequency, dwDataRate );

}

//------------------------------------------------------------------------------
/// Report the CMD_NOT_SUPPORTED error to the host
//------------------------------------------------------------------------------
static void vCCIDCommandNotSupported( void )
{
	// Command not supported
	// vCCIDReportError(CMD_NOT_SUPPORTED);

	TRACE_DEBUG("CMD_NOT_SUPPORTED\n\r");

	// Header fields settings
	ccidDriver.sCcidMessage.bMessageType = RDR_TO_PC_SLOTSTATUS;
	ccidDriver.sCcidMessage.wLength      = 0;
	ccidDriver.sCcidMessage.bSpecific    = 0;

	ccidDriver.sCcidMessage.bStatus |= ICC_CS_FAILED;

	// Send the response to the host
	//vCCIDSendResponse();
}

//------------------------------------------------------------------------------
/// Sent CCID response on USB
//------------------------------------------------------------------------------
static void vCCIDSendResponse( void )
{
	unsigned char bStatus;
	TRACE_DEBUG(".");

	do {
		bStatus = CCID_Write((void*)&ccidDriver.sCcidMessage,
					          ccidDriver.sCcidMessage.bSizeToSend, 0, 0 );
	} while (bStatus != USBD_STATUS_SUCCESS);

	TRACE_DEBUG("bStatus: 0x%x\n\r", bStatus);
}


//------------------------------------------------------------------------------
///  Description: CCID Command dispatcher
//------------------------------------------------------------------------------
static void CCIDCommandDispatcher( void *pArg, uint8_t status, uint32_t transferred, uint32_t remaining )
{
	unsigned char MessageToSend = 0;

	if (status != USBD_STATUS_SUCCESS) {
		TRACE_ERROR("USB error: %d", status);
		return;
	}
	TRACE_DEBUG("Command: 0x%X 0x%x 0x%X 0x%X 0x%X 0x%X 0x%X\n\r\n\r",
				   (unsigned int)ccidDriver.sCcidCommand.bMessageType,
				   (unsigned int)ccidDriver.sCcidCommand.wLength,
				   (unsigned int)ccidDriver.sCcidCommand.bSlot,
				   (unsigned int)ccidDriver.sCcidCommand.bSeq,
				   (unsigned int)ccidDriver.sCcidCommand.bSpecific_0,
				   (unsigned int)ccidDriver.sCcidCommand.bSpecific_1,
				   (unsigned int)ccidDriver.sCcidCommand.bSpecific_2);

	// Check the slot number
	if ( ccidDriver.sCcidCommand.bSlot > 0 ) {

		TRACE_ERROR("BAD_SLOT_NUMBER\n\r");
	}

	TRACE_INFO("typ=0x%X\n\r", ccidDriver.sCcidCommand.bMessageType);

	ccidDriver.sCcidMessage.bStatus = 0;

	ccidDriver.sCcidMessage.bSeq  = ccidDriver.sCcidCommand.bSeq;
	ccidDriver.sCcidMessage.bSlot = ccidDriver.sCcidCommand.bSlot;

	ccidDriver.sCcidMessage.bSizeToSend = sizeof(S_ccid_bulk_in_header)-(ABDATA_SIZE+1);


	// Command dispatcher
	switch ( ccidDriver.sCcidCommand.bMessageType ) {

		case PC_TO_RDR_ICCPOWERON:
			PCtoRDRIccPowerOn();
			MessageToSend = 1;
			break;

		case PC_TO_RDR_ICCPOWEROFF:
			PCtoRDRIccPowerOff();
			MessageToSend = 1;
			break;

		case PC_TO_RDR_GETSLOTSTATUS:
			PCtoRDRGetSlotStatus();
			MessageToSend = 1;
			break;

		case PC_TO_RDR_XFRBLOCK:
			PCtoRDRXfrBlock();
			MessageToSend = 1;
			break;

		case PC_TO_RDR_GETPARAMETERS:
			PCtoRDRGetParameters();
			MessageToSend = 1;
			break;

		case PC_TO_RDR_RESETPARAMETERS:
			PCtoRDRResetParameters();
			MessageToSend = 1;
			break;

		case PC_TO_RDR_SETPARAMETERS:
			PCtoRDRSetParameters();
			MessageToSend = 1;
			break;

		case PC_TO_RDR_ESCAPE:
			PCtoRDREscape();
			MessageToSend = 1;
			break;

		case PC_TO_RDR_ICCCLOCK:
			PCtoRDRICCClock();
			MessageToSend = 1;
			break;

		case PC_TO_RDR_T0APDU:
			// Only CCIDs reporting a short or extended APDU level in the dwFeatures 
			// field of the CCID class descriptor may take this command into account.
			if( (CCID_FEATURES_EXC_SAPDU == (CCID_FEATURES_EXC_SAPDU&configurationDescriptorsFS->ccid.dwFeatures))
			|| (CCID_FEATURES_EXC_APDU  == (CCID_FEATURES_EXC_APDU &configurationDescriptorsFS->ccid.dwFeatures)) ) {

				// command supported
				PCtoRDRtoAPDU();
			}
			else {
				// command not supported
				TRACE_INFO("Not supported: PC_TO_RDR_T0APDU\n\r");
				vCCIDCommandNotSupported();
			}
			MessageToSend = 1;
			break;

		case PC_TO_RDR_SECURE:
			PCtoRDRSecure();
			MessageToSend = 1;
			break;

		case PC_TO_RDR_MECHANICAL:
			PCtoRDRMechanical();
			MessageToSend = 1;
			break;

		case PC_TO_RDR_ABORT:
			PCtoRDRAbort();
			MessageToSend = 1;
			break;

		case PC_TO_RDR_SETDATARATEANDCLOCKFREQUENCY:
			PCtoRDRSetDataRateAndClockFrequency();
			MessageToSend = 1;
			break;

		default:
			TRACE_DEBUG("default: Not supported: 0x%X\n\r", ccidDriver.sCcidCommand.bMessageType);
			vCCIDCommandNotSupported();
			MessageToSend = 1;
			break;

	}

	if( MessageToSend == 1 ) {
		vCCIDSendResponse();
	}
}


//------------------------------------------------------------------------------
/// SETUP request handler for a CCID device
/// \param pRequest Pointer to a USBGenericRequest instance
//------------------------------------------------------------------------------
static void CCID_RequestHandler(const USBGenericRequest *pRequest)
{
	TRACE_DEBUG("CCID_RHl\n\r");

	// Check if this is a class request
	if (USBGenericRequest_GetType(pRequest) == USBGenericRequest_CLASS) {

		// Check if the request is supported
		switch (USBGenericRequest_GetRequest(pRequest)) {

			case CCIDGenericRequest_ABORT:
				TRACE_DEBUG("CCIDGenericRequest_ABORT\n\r");
				break;

			case CCIDGenericRequest_GET_CLOCK_FREQUENCIES:
				TRACE_DEBUG("Not supported: CCIDGenericRequest_GET_CLOCK_FREQUENCIES\n\r");
				// A CCID with bNumClockSupported equal to 00h does not have 
				// to support this request
				break;

			case CCIDGenericRequest_GET_DATA_RATES:
				TRACE_DEBUG("Not supported: CCIDGenericRequest_GET_DATA_RATES\n\r");
				// A CCID with bNumDataRatesSupported equal to 00h does not have 
				// to support this request.
				break;

			default:
				TRACE_WARNING( "CCIDDriver_RequestHandler: Unsupported request (%d)\n\r",
					                                USBGenericRequest_GetRequest(pRequest));
				USBD_Stall(0);
		}
	}

	else if (USBGenericRequest_GetType(pRequest) == USBGenericRequest_STANDARD) {

		// Forward request to the standard handler
		USBDDriver_RequestHandler(USBD_GetDriver(), pRequest);
	}
	else {

		// Unsupported request type
		TRACE_WARNING( "CCIDDriver_RequestHandler: Unsupported request type (%d)\n\r",
					                                USBGenericRequest_GetType(pRequest));
		USBD_Stall(0);
	}
}


//------------------------------------------------------------------------------
//      Exported functions
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
/// Optional callback re-implementation
//------------------------------------------------------------------------------
#if !defined(NOAUTOCALLBACK)
// not static function
void USBDCallbacks_RequestReceived(const USBGenericRequest *request)
{
	CCID_RequestHandler(request);
}
#endif


//------------------------------------------------------------------------------
/// Handles SmartCart request
//------------------------------------------------------------------------------
void CCID_SmartCardRequest( void )
{
	unsigned char bStatus;
	TRACE_DEBUG("CCID_req\n\r");

	do {

		bStatus = CCID_Read( (void*)&ccidDriver.sCcidCommand,
					         sizeof(S_ccid_bulk_out_header),
					         (TransferCallback)&CCIDCommandDispatcher,
					         (void*)0 );
	}
	while (0);
}


//------------------------------------------------------------------------------
/// Reads data from the Data OUT endpoint
/// \param pBuffer   Buffer to store the received data
/// \param dLength   data buffer length
/// \param fCallback Optional callback function
/// \param pArgument Optional parameter for the callback function
/// \return USBD_STATUS_LOCKED or USBD_STATUS_SUCCESS
//------------------------------------------------------------------------------
unsigned char CCID_Read(void *pBuffer,
					    unsigned int dLength,
					    TransferCallback fCallback,
					    void *pArgument)
{
	return USBD_Read(CCID_EPT_DATA_OUT, pBuffer, dLength, fCallback, pArgument);
}

//------------------------------------------------------------------------------
/// Sends data through the Data IN endpoint
/// \param pBuffer   Buffer holding the data to transmit
/// \param dLength   Length of data buffer
/// \param fCallback Optional callback function
/// \param pArgument Optional parameter for the callback function
/// \return USBD_STATUS_LOCKED or USBD_STATUS_SUCCESS
//------------------------------------------------------------------------------
unsigned char CCID_Write(void *pBuffer,
					     unsigned int dLength,
					     TransferCallback fCallback,
					     void *pArgument)
{
	return USBD_Write(CCID_EPT_DATA_IN, pBuffer, dLength, fCallback, pArgument);
}

//------------------------------------------------------------------------------
/// Sends data through the interrupt endpoint, ICC insertion event
/// RDR_to_PC_NotifySlotChange
/// \return USBD_STATUS_LOCKED or USBD_STATUS_SUCCESS
//------------------------------------------------------------------------------
unsigned char CCID_Insertion( void )
{
	TRACE_DEBUG(".");

	// Build the Interrupt-IN message
	ccidDriver.BufferINT[0] = RDR_TO_PC_NOTIFYSLOTCHANGE;
	ccidDriver.BufferINT[1] = ICC_INSERTED_EVENT;
	ccidDriver.SlotStatus   = ICC_INSERTED_EVENT;

	// Notify the host that a ICC is inserted
	return USBD_Write( CCID_EPT_NOTIFICATION, ccidDriver.BufferINT, 2, 0, 0 );
}

//------------------------------------------------------------------------------
/// Sends data through the interrupt endpoint, ICC removal event
/// RDR_to_PC_NotifySlotChange
/// \return USBD_STATUS_LOCKED or USBD_STATUS_SUCCESS
//------------------------------------------------------------------------------
unsigned char CCID_Removal( void )
{
	TRACE_DEBUG(".");

	// Build the Interrupt-IN message
	ccidDriver.BufferINT[0] = RDR_TO_PC_NOTIFYSLOTCHANGE;
	ccidDriver.BufferINT[1] = ICC_NOT_PRESENT;
	ccidDriver.SlotStatus   = ICC_NOT_PRESENT;

	// Notify the host that a ICC is inserted
	return USBD_Write( CCID_EPT_NOTIFICATION, ccidDriver.BufferINT, 2, 0, 0 );
}

//------------------------------------------------------------------------------
/// Interrupt-IN Messages
/// This message is sent when any bit in the bHardwareErrorCode field is set. 
/// If this message is sent when there is no "outstanding" command, the bSeq
/// field will be undefined.
/// \param bSlot ICC slot number
/// \param bSeq  Sequence number of the bulk OUT command when the hardware error
/// occured
/// \param bHardwareErrorCode Hardware error code
/// \return USBD_STATUS_LOCKED or USBD_STATUS_SUCCESS
//------------------------------------------------------------------------------
unsigned char RDRtoPCHardwareError( unsigned char bSlot, 
					                unsigned char bSeq, 
					                unsigned char bHardwareErrorCode )
{
	TRACE_DEBUG(".");

	// Build the Interrupt-IN message
	ccidDriver.BufferINT[0] = RDR_TO_PC_HARDWAREERROR;
	ccidDriver.BufferINT[1] = bSlot;
	ccidDriver.BufferINT[2] = bSeq;
	ccidDriver.BufferINT[3] = bHardwareErrorCode;

	// Notify the host that a ICC is inserted
	return USBD_Write( CCID_EPT_NOTIFICATION, ccidDriver.BufferINT, 4, 0, 0 );
}

#endif /* HAVE_CCID */
