/*
* Proyecto CCIDroid. Driver para utilizacion de tarjetas CCID en el sistema operativo
* Android.
*
* El proyecto CCIDroid es un conector para la comunicacion entre sistemas Android y
* lectores de SmartCard USB segun el estandar CCID. Diseno inicial desarrollado para
* su integracion con el Controlador Java de la Secretaria de Estado de Administraciones
* Publicas para el DNI electronico.
*
* Copyright (C) 2012 Instituto Nacional de las Tecnologias de la Comunicacion (INTECO)
*
* Este programa es software libre y utiliza un licenciamiento dual (LGPL 2.1+
* o EUPL 1.1+), lo cual significa que los usuarios podran elegir bajo cual de las
* licencias desean utilizar el codigo fuente. Su eleccion debera reflejarse
* en las aplicaciones que integren o distribuyan el Controlador, ya que determinara
* su compatibilidad con otros componentes.
*
* El Controlador puede ser redistribuido y/o modificado bajo los terminos de la
* Lesser GNU General Public License publicada por la Free Software Foundation,
* tanto en la version 2.1 de la Licencia, o en una version posterior.
*
* El Controlador puede ser redistribuido y/o modificado bajo los terminos de la
* European Union Public License publicada por la Comision Europea,
* tanto en la version 1.1 de la Licencia, o en una version posterior.
*
* Deberia recibir una copia de la GNU Lesser General Public License, si aplica, junto
* con este programa. Si no, consultelo en <http://www.gnu.org/licenses/>.
*
* Deberia recibir una copia de la European Union Public License, si aplica, junto
* con este programa. Si no, consultelo en <http://joinup.ec.europa.eu/software/page/eupl>.
*
* Este programa es distribuido con la esperanza de que sea util, pero
* SIN NINGUNA GARANTIA; incluso sin la garantia implicita de comercializacion
* o idoneidad para un proposito particular.
*/
package es.inteco.labs.android.usb.device;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.os.SystemClock;
import es.gob.jmulticard.HexUtils;
import es.inteco.labs.android.usb.device.ccid.instruction.UsbCommand;
import es.inteco.labs.android.usb.device.ccid.response.UsbResponse;
import es.inteco.labs.android.usb.device.exception.UsbCommandTransmissionException;
import es.inteco.labs.android.usb.device.exception.UsbSmartCardChannelException;
/** Representa el canal a traves del cual se procesan las peticiones USB para un dispositivo concreto.
* @author Jose Luis Escanciano Garcia */
final class SmartCardChannel {
private UsbEndpoint endPointIn;
private UsbEndpoint endPointOut;
private final UsbDeviceConnection usbDeviceConnection;
private static final int MAX_SIZE_APDU_T0 = 260;
private static final int MAX_TIME_EXTENSION_RETRIES = 16;
private static final int TIME_EXTENSION_RETRY_MS = 200;
private static final int TIMEOUT_MS = 5000;
/** Constructor. Inicia los EndPoints del Interfaz del dispositivo
* @param usbDevCon
* @param usbInterface */
protected SmartCardChannel(final UsbDeviceConnection usbDevCon, final UsbInterface usbInterface) {
this.usbDeviceConnection = usbDevCon;
for (int i = 0; i < usbInterface.getEndpointCount(); i++) {
final UsbEndpoint usbEndPoint = usbInterface.getEndpoint(i);
if (usbEndPoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
if (usbEndPoint.getDirection() == UsbConstants.USB_DIR_IN) {
this.endPointIn = usbEndPoint;
}
else if (usbEndPoint.getDirection() == UsbConstants.USB_DIR_OUT) {
this.endPointOut = usbEndPoint;
}
}
}
}
/** Transmite un comando USB a través del interfaz del dispositivo.
* @param command Comando USB
* @return Respuesta USB al comando
* @throws UsbCommandTransmissionException
* @throws UsbSmartCardChannelException */
UsbResponse transmit(final UsbCommand command) throws UsbCommandTransmissionException, UsbSmartCardChannelException{
final int responseLength = MAX_SIZE_APDU_T0 + UsbResponse.USB_HEADER_BASE_SIZE;
//Se envia el comando
usbSendCommand(command.getBytes());
//Se recoge la respuesta
UsbResponse usbResponse = new UsbResponse(command, usbRetrieveResponse(responseLength));
//Si el dispositivo CCID solicita Time Extension hay que esperar a obtener la respuesta nueva Request Wait
int timeExtensionRetryCounter = 0;
while(usbResponse.getCommandStatus() == UsbResponse.COMMAND_STATUS_TIME_EXTENSION && timeExtensionRetryCounter++ < MAX_TIME_EXTENSION_RETRIES){
//Se esperan TIME_EXTENSION_RETRY_MS ms antes de solicitar de nuevo la respuesta
SystemClock.sleep(TIME_EXTENSION_RETRY_MS);
usbResponse = new UsbResponse(command, usbRetrieveResponse(responseLength));
}
//Comprobar si la respuesta corresponde con la peticion
if(command.getInstructionCount() != usbResponse.getSequenceNumber()){
//TODO: Si esto pasa, lo mejor es reiniciar la conexion con el dispositivo
throw new UsbSmartCardChannelException("El ID de secuencia del comando [" + command.getInstructionCount() + "] no coincide con el de la respuesta [" + usbResponse.getSequenceNumber() + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
return usbResponse;
}
/** Envía un comando USB a través del Endpoint OUT.
* @param data Comando a enviar
* @throws UsbCommandTransmissionException */
private void usbSendCommand(final byte[] data) throws UsbCommandTransmissionException {
final int dataTransferred = this.usbDeviceConnection.bulkTransfer(this.endPointOut, data, data.length, TIMEOUT_MS);
if(!(dataTransferred == 0 || dataTransferred==data.length)) {
throw new UsbCommandTransmissionException("Error al transmitir el comando [" + dataTransferred + " ; " + data.length + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
/** Recoge la respuesta de un comando USB a través del Endpoint IN
* @param responseSize Tamaño de respuesta esperado
* @return Respuesta al comando USB
* @throws UsbCommandTransmissionException */
private byte[] usbRetrieveResponse(final int responseSize) throws UsbCommandTransmissionException{
final byte[] responseBuffer = new byte[responseSize];
final int dataTransferred = this.usbDeviceConnection.bulkTransfer(this.endPointIn, responseBuffer, responseBuffer.length, TIMEOUT_MS);
if(dataTransferred >= 0){
return HexUtils.subArray(responseBuffer, 0, dataTransferred);
}
throw new UsbCommandTransmissionException("Error al recibir respuesta del comando [" + dataTransferred + "]"); //$NON-NLS-1$ //$NON-NLS-2$
}
}