package es.gob.jmulticard.android.nfc; import java.io.ByteArrayOutputStream; import java.io.IOException; import android.nfc.Tag; import android.nfc.tech.IsoDep; import es.gob.jmulticard.HexUtils; import es.gob.jmulticard.apdu.CommandApdu; import es.gob.jmulticard.apdu.ResponseApdu; import es.gob.jmulticard.apdu.connection.ApduConnection; import es.gob.jmulticard.apdu.connection.ApduConnectionException; import es.gob.jmulticard.apdu.connection.ApduConnectionProtocol; import es.gob.jmulticard.apdu.connection.CardConnectionListener; import es.gob.jmulticard.apdu.dnie.VerifyApduCommand; import es.gob.jmulticard.apdu.iso7816four.GetResponseApduCommand; /** Conexión con lector de tarjetas inteligentes implementado sobre NFC para Android. * @author Tomás García-Merás */ public final class AndroidNfcConnection implements ApduConnection { static final String TAG = "NfcConnection"; //$NON-NLS-1$ private final IsoDep misoDep; /** Obtiene la conexión NFC actual. * @return El objeto IsoDep para la conexión por NFC. */ public IsoDep getIsoDep() { return this.misoDep; } /** * Contructor por defecto de la clase para la gestión de la conexión por NFC. */ public AndroidNfcConnection() { this.misoDep = null; } /** Contructor de la clase para la gestión de la conexión por NFC. * @param tag Tag para obtener el objeto IsoDep y establecer la conexión. * @throws IOException Se produduce ante un fallo en el establecimiento de la conexión. */ public AndroidNfcConnection(final Tag tag) throws IOException { if (tag == null) { throw new IllegalArgumentException("El tag NFC no puede ser nulo"); //$NON-NLS-1$ } this.misoDep = IsoDep.get(tag); this.misoDep.connect(); this.misoDep.setTimeout(3000); } @Override public ResponseApdu transmit(final CommandApdu command) throws ApduConnectionException { if (this.misoDep == null) { throw new ApduConnectionException("No se puede transmitir sobre una conexion NFC cerrada"); //$NON-NLS-1$ } if (command == null) { throw new IllegalArgumentException("No se puede transmitir una APDU nula"); //$NON-NLS-1$ } if(!this.misoDep.isConnected()) { try { this.misoDep.connect(); } catch (final IOException e) { throw new ApduConnectionException("Se ha producido un problema al intentar establecer la conexion por NFC"); //$NON-NLS-1$ } } try { ResponseApdu response = null; if (command instanceof VerifyApduCommand) { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final byte[] bcomm = command.getBytes(); final byte[] bdata = command.getData(); baos.write(bcomm, 0, bcomm.length - 2); baos.write(new byte[]{(byte)bdata.length}); baos.write(bdata); byte[] bResp = new byte[]{0, 0}; try { bResp = this.misoDep.transceive(baos.toByteArray()); if (bResp.length < 2) { throw new ApduConnectionException("No se ha recibido respuesta al env\u00edo del comando."); //$NON-NLS-1$ } } catch (final IOException e) { e.printStackTrace(); } response = new ResponseApdu(bResp); } else { byte[] bResp = new byte[]{0, 0}; try { bResp = this.misoDep.transceive(command.getBytes()); if (bResp.length < 2) { throw new ApduConnectionException("No se ha recibido respuesta al env\u00edo del comando."); //$NON-NLS-1$ } } catch (final IOException e) { e.printStackTrace(); } response = new ResponseApdu(bResp); } if (response.getStatusWord().getMsb() == 97) { if (response.getData().length > 0) { final byte[] data = response.getData(); final byte[] additionalData = this.transmit(new GetResponseApduCommand((byte) 0, response.getStatusWord().getLsb())).getBytes(); final byte[] fullResponse = new byte[data.length + additionalData.length]; System.arraycopy(data, 0, fullResponse, 0, data.length); System.arraycopy(additionalData, 0, fullResponse, data.length, additionalData.length); return new ResponseApdu(fullResponse); } return this.transmit(new GetResponseApduCommand((byte) 0, response.getStatusWord().getLsb())); } if (response.getStatusWord().getMsb() == 108 && command.getCla() == 0) { command.setLe(response.getStatusWord().getLsb()); return this.transmit(command); } return response; } catch (final Exception e) { throw new ApduConnectionException("Error tratando de transmitir la APDU " + HexUtils.hexify(command.getBytes(), true) + " al lector NFC.", e); //$NON-NLS-1$ //$NON-NLS-2$ } } @Override public void open() throws ApduConnectionException { try { if (!this.misoDep.isConnected()) { this.misoDep.connect(); } } catch (final Exception e) { throw new ApduConnectionException("Error intentando abrir la comunicaci\u00f3n NFC contra la tarjeta.", e); //$NON-NLS-1$ } } @Override public void close() throws ApduConnectionException { //No se cierran las conexiones por NFC } @Override public byte[] reset() throws ApduConnectionException { //No se cierran las conexiones por NFC if (this.misoDep != null) { if (this.misoDep.getHistoricalBytes() != null) { return this.misoDep.getHistoricalBytes(); } return this.misoDep.getHiLayerResponse(); } throw new ApduConnectionException("Error indefinido reiniciando la conexion con la tarjeta"); //$NON-NLS-1$ } @Override public void addCardConnectionListener(final CardConnectionListener ccl) { throw new UnsupportedOperationException(); } @Override public void removeCardConnectionListener(final CardConnectionListener ccl) { throw new UnsupportedOperationException(); } @Override public long[] getTerminals(final boolean onlyWithCardPresent) throws ApduConnectionException { return new long[] { 0 }; } @Override public String getTerminalInfo(final int terminal) throws ApduConnectionException { return "Interfaz ISO-DEP NFC de Android"; //$NON-NLS-1$ } @Override public void setTerminal(final int t) { // Vacio } @Override public boolean isOpen() { return this.misoDep.isConnected(); } @Override public void setProtocol(final ApduConnectionProtocol p) { // No hace nada } }