Package uk.co.nimp.scard

Source Code of uk.co.nimp.scard.GenericTerminal$ApduTimeoutTask

/*
*/
/**
*
* @author Sebastien Riou
*/

package uk.co.nimp.scard;

import com.atolsystems.atolutilities.AStringUtilities;
import com.atolsystems.atolutilities.StopRequestFromUserException;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.smartcardio.CardException;
import javax.smartcardio.CardNotPresentException;
import javax.swing.JOptionPane;
import uk.co.nimp.scard.log.ScardPrintStreamLogHandler;
import uk.co.nimp.smartcard.AnswerToReset;
import uk.co.nimp.smartcard.Apdu;
import uk.co.nimp.smartcard.UnexpectedCardResponseException;


public abstract class GenericTerminal implements WithBasicLoggers {
   
    public final static int PROTOCOL_UNDEFINED=            0x00000000;
    public final static int PROTOCOL_T_0=                  0x00000001;
    public final static int PROTOCOL_T_1=                  0x00000002;
    public final static int PROTOCOL_T_15=                 0x00008000;
    public final static int PROTOCOL_T_CL_A=               0x00010000;
    public final static int PROTOCOL_T_CL_B=               0x00020000;
    public final static int PROTOCOL_DIRECT=               0x08000000;
    public final static int PROTOCOL_ANY_STD_CL=           0x00030000;
    public final static int PROTOCOL_ANY_STD_CONTACT=      0x00000003;
    public final static int PROTOCOL_ANY_STD=              0x00030003;
    public final static int PROTOCOL_ANY=                  0x0FFFFFFF;

    public final static String PROTOCOL_NAME_UNDEFINED="T=Undefined";
    public final static String PROTOCOL_NAME_T_0="T=0";
    public final static String PROTOCOL_NAME_T_1="T=1";
    public final static String PROTOCOL_NAME_T_15="T=15";
    public final static String PROTOCOL_NAME_T_CL_A="T=CLA";
    public final static String PROTOCOL_NAME_T_CL_B="T=CLB";
    public final static String PROTOCOL_NAME_DIRECT="T=Direct";
    public final static String PROTOCOL_NAME_ANY_STD_CL="T=CL";
    public final static String PROTOCOL_NAME_ANY="*";

    public final static int ACTIVATION_FORCE_COLD = 0x01;
    public final static int ACTIVATION_FORCE_WARM = 0x02;
    public final static int ACTIVATION_ANY = 0x04;

    public final static int COM_RUN = 0x01;
    public final static int COM_STEP = 0x02;

    protected boolean autoGetResponse =false;


    boolean unsupportedExceptionEnabled;
    Set<ScardLogHandler> logHandlers;
    final protected String name;
    protected State state;
    protected AnswerToReset atr;
    protected int protocol;
    protected boolean connected;
    protected int activationCounter=0;;
    protected boolean negociateComSpeed = true;
    protected Map<String,String> env;

    public GenericTerminal(String name) {
        this.unsupportedExceptionEnabled = true;
        this.logHandlers = new HashSet<ScardLogHandler>();
        this.name = name;
        state=State.UNDEFINED;
        protocol=PROTOCOL_UNDEFINED;
        logsEnabled=true;
        player.setCheckConnection(false);
        env=new HashMap<String,String>();
    }

    public Map<String, String> getEnv() {
        return env;
    }

    public String getEnvVariable(String name){
        String out=env.get(name);
        if(null==out) out="";
        return out;
    }

    public int getEnvSize() {
        return env.size();
    }

    public String removeEnvVariable(String name) {
        return env.remove(name);
    }

    public String putEnvVariable(String name, String value) {
        return env.put(name, value);
    }

    public boolean containsEnvVariable(String name) {
        return env.containsKey(name);
    }

    public void clearEnv() {
        env.clear();
    }

    //Terminal with timers should override this method to have better accuracy
    public void delay(long delayNs){
        long start=System.nanoTime();
        long now;
        do{
            now=System.nanoTime();
        }while(now-start<delayNs);
    }

    /**
     * Get the connection counter for this terminal.
     * The counter is incremented each time the card is activated
     * @return the value of the counter
     */
    public int getActivationCounter() {
        return activationCounter;
    }

    static public String getProtocolName(int protocol){
        switch(protocol){
            case PROTOCOL_UNDEFINED:
                return PROTOCOL_NAME_UNDEFINED;
            case PROTOCOL_T_0:
                return PROTOCOL_NAME_T_0;
            case PROTOCOL_T_1:
                return PROTOCOL_NAME_T_1;
            case PROTOCOL_T_15:
                return PROTOCOL_NAME_T_15;
            case PROTOCOL_T_CL_A:
                return PROTOCOL_NAME_T_CL_A;
            case PROTOCOL_T_CL_B:
                return PROTOCOL_NAME_T_CL_B;
            case PROTOCOL_DIRECT:
                return PROTOCOL_NAME_DIRECT;
            case PROTOCOL_ANY_STD_CL:
                return PROTOCOL_NAME_ANY_STD_CL;
            case PROTOCOL_ANY:
                return PROTOCOL_NAME_ANY;
            default:
                throw new IllegalArgumentException("protocol = "+Integer.toHexString(protocol));
        }
    }

    static public int getProtocolCode(String protocol){
        if(protocol.equals(PROTOCOL_NAME_T_0))
            return PROTOCOL_T_0;
        if(protocol.equals(PROTOCOL_NAME_T_1))
            return PROTOCOL_T_1;
        if(protocol.equals(PROTOCOL_NAME_T_15))
            return PROTOCOL_T_15;
        if(protocol.equals(PROTOCOL_NAME_T_CL_A))
            return PROTOCOL_T_CL_A;
        if(protocol.equals(PROTOCOL_NAME_T_CL_B))
            return PROTOCOL_T_CL_B;
        if(protocol.equals(PROTOCOL_NAME_DIRECT))
            return PROTOCOL_DIRECT;
        if(protocol.equals(PROTOCOL_NAME_ANY_STD_CL))
            return PROTOCOL_ANY_STD_CL;
        if(protocol.equals(PROTOCOL_NAME_ANY))
            return PROTOCOL_ANY;
        throw new IllegalArgumentException("protocol = "+protocol);
    }

    /**
     * Returns the unique name of this terminal.
     *
     * @return the unique name of this terminal.
     */
    public String getName() {
  return name;
    }

    public AnswerToReset getAtr() {
        return atr;
    }

    public int getProtocol(){
        return protocol;
    }

    /**
     * Returns whether a card is present in this terminal.
     *
     * @return whether a card is present in this terminal.
     *
     * @throws CardException if the status could not be determined
     */
    public abstract boolean isCardPresent() throws ScardException;

    /**
     * Waits until a card is present in this terminal or the timeout
     * expires. If the method returns due to an expired timeout, it returns
     * false. Otherwise it return true.
     *
     * <P>If a card is present in this terminal when this
     * method is called, it returns immediately.
     *
     * @param timeout if positive, block for up to <code>timeout</code>
     *   milliseconds; if zero, block indefinitely; must not be negative
     * @return false if the method returns due to an expired timeout,
     *   true otherwise.
     *
     * @throws IllegalArgumentException if timeout is negative
     * @throws CardException if the operation failed
     */
    public abstract boolean waitForCardPresent(long timeout) throws ScardException;

    /**
     * Waits until a card is absent in this terminal or the timeout
     * expires. If the method returns due to an expired timeout, it returns
     * false. Otherwise it return true.
     *
     * <P>If no card is present in this terminal when this
     * method is called, it returns immediately.
     *
     * @param timeout if positive, block for up to <code>timeout</code>
     *   milliseconds; if zero, block indefinitely; must not be negative
     * @return false if the method returns due to an expired timeout,
     *   true otherwise.
     *
     * @throws IllegalArgumentException if timeout is negative
     * @throws CardException if the operation failed
     */
    public abstract boolean waitForCardAbsent(long timeout) throws ScardException;

    public synchronized void coldConnect()throws ScardException, CardNotPresentException{
        connect(PROTOCOL_ANY, ACTIVATION_FORCE_COLD);
    }

    public synchronized void warmConnect()throws ScardException, CardNotPresentException{
        connect(PROTOCOL_ANY, ACTIVATION_FORCE_WARM);
    }

    public synchronized void connect(int activation)throws ScardException,CardNotPresentException{
        connect(PROTOCOL_ANY, activation);
    }

    public synchronized void connect(String protocol)throws ScardException,CardNotPresentException{
        connect(getProtocolCode(protocol), ACTIVATION_FORCE_COLD);
    }

   //cold/warm reset
    public abstract void connectImpl(int protocol, int activation)throws ScardException, CardNotPresentException ;
    public synchronized void connect(int protocol, int activation)throws ScardException, CardNotPresentException {
        if((GenericTerminal.ACTIVATION_FORCE_WARM==activation) && (false==connected))
            throw new ScardException("Warm reset cannot be performed when the card is not activated already.");
        beforeConnectScript.play();

        boolean localConnected=false;
        switch(activation){
            case ACTIVATION_FORCE_COLD:
                logLine(ScardLogHandler.LOG_CARD_EVENT,"COLD CONNECT");
                break;
            case ACTIVATION_FORCE_WARM:
                logLine(ScardLogHandler.LOG_CARD_EVENT,"WARM CONNECT");
                break;
            default:
                logLine(ScardLogHandler.LOG_CARD_EVENT,"CONNECT");
        }
        try{
            connectImpl(protocol, activation);
            activationCounter++;
            localConnected=true;
        } catch(ScardException e){
            logLine(ScardLogHandler.LOG_ERROR,"Failed to connect: "+e.getMessage());
            if(null!=e.getCause())
                logLine(ScardLogHandler.LOG_ERROR,e.getCause().getMessage());
            throw e;
        } catch(CardNotPresentException e){
            logLine(ScardLogHandler.LOG_ERROR,"Failed to connect: "+e.getMessage());
            if(null!=e.getCause())
                logLine(ScardLogHandler.LOG_ERROR,e.getCause().getMessage());
            throw e;
        } catch(Exception e){
            logLine(ScardLogHandler.LOG_ERROR,"Failed to connect: "+e.getMessage());
            if(null!=e.getCause())
                logLine(ScardLogHandler.LOG_ERROR,e.getCause().getMessage());
            throw new RuntimeException(e);
        } finally {
            connected = localConnected;
        }
        afterConnectScript.play();
    }

    public boolean isNegociateComSpeed() {
        return negociateComSpeed;
    }

    public void setNegociateComSpeed(boolean autoPps) {
        this.negociateComSpeed = autoPps;
    }

    class DummyStatusDisplayer implements StatusDisplayer{
        public void setText(String msg) {
            //do nothing
        }
    }

    ScriptPlayer player=new ScriptPlayer(new DummyStatusDisplayer());

    public boolean isUserCancelled() {
        return player.isUserCancelled();
    }

    boolean errorOccured=false;
    public boolean isErrorOccured() {
        return player.isErrorOccured()|errorOccured;
    }

    final EventScript beforeDisconnectScript=new EventScript();//can be used to dump chip state before disconnection or set a parameter for next session
    final EventScript beforeConnectScript=new EventScript();//can be used to set voltage and frequency
    final EventScript afterConnectScript=new EventScript();//can be used to init the chip with different conditions before running a test
    final EventScript unexpectedCardResponseScript=new EventScript();//allow diagnostic
    //final EventScript unexpectedCardResponseResumingScript=new EventScript();//allow to resume operations
    final EventScript apduTimeoutScript=new EventScript();//will be launched after forced disconnection
    final EventScript apduMiscErrorScript=new EventScript();//can be use to resume operation after an error in one apdu. script must first establish connection.

    public void setBeforeDisconnectScript(List<? extends Object> script){
        beforeDisconnectScript.setScript(script);
    }
    public void setBeforeConnectScript(List<? extends Object> script){
        beforeConnectScript.setScript(script);
    }
    public void setAfterConnectScript(List<? extends Object> script){
        afterConnectScript.setScript(script);
    }
    public void setUnexpectedCardResponseScript(List<? extends Object> script){
        unexpectedCardResponseScript.setScript(script);
    }
    /*public void setUnexpectedCardResponseResumingScript(List<? extends Object> script){
        unexpectedCardResponseResumingScript.setScript(script);
    }*/
    public void setApduTimeoutScript(List<? extends Object> script){
        apduTimeoutScript.setScript(script);
    }
    public void setApduMiscErrorScript(List<? extends Object> script){
        apduMiscErrorScript.setScript(script);
    }

    public void playUnexpectedCardResponseScript() throws ScardException, StopRequestFromUserException, UnexpectedCardResponseException, CardNotPresentException {
        unexpectedCardResponseScript.play();
    }
   
    class EventScript{
        List<? extends Object> script;
        boolean running;
        void setScript(List<? extends Object> script){
            while(running){
                Thread.yield();
            }
            this.script=script;
        }
        void play() throws ScardException, StopRequestFromUserException, UnexpectedCardResponseException, CardNotPresentException{
            if(running){
                //System.out.println("EventScript already running! "+this.getClass());
                return;
            }
            running=true;
            try{
                //System.out.println("Start EventScript "+this.getClass());
                player.play(GenericTerminal.this, script);
                //System.out.println("End of EventScript "+this.getClass());
            }catch (IOException ex) {
                throw new RuntimeException(ex);
            }finally{
                running=false;
            }
        }

        List<? extends Object> getScript() {
            return script;
        }
    }

    protected void playEventScript(List<? extends Object> eventScript){

    }

    //power off
    abstract protected void disconnectImpl() throws ScardException;
    public synchronized void disconnect() throws ScardException, StopRequestFromUserException, UnexpectedCardResponseException, CardNotPresentException{
        beforeDisconnectScript.play();
        log(ScardLogHandler.LOG_CARD_EVENT,"DISCONNECT\n");
        connected=false;
        disconnectImpl();
    }

    abstract protected void forceDisconnectImpl() throws ScardException;
    public void forceDisconnection() throws ScardException{
        log(ScardLogHandler.LOG_CARD_EVENT,"FORCED DISCONNECTION\n");
        connected=false;
        forceDisconnectImpl();
    }

    Long lastApduTotalTime;
    public long getLastApduTotalTime(){
        return lastApduTotalTime;
    }

    abstract public void sendApduImpl(Apdu apdu)throws ScardException, UnexpectedCardResponseException;

    public /*synchronized*/ void sendApdu(Apdu apdu)throws ScardException, UnexpectedCardResponseException, CardNotPresentException{
        boolean localErrorHappened=true;
        try{
            try{
                if(apdu.getExpectedLe()>0x100)
                    throw new RuntimeException("le>0x100 not supported yet");
                logCommand(apdu);
                Timer apduTimer=null;
                ApduTimeoutTask apduTimeoutTask=null;
                if(timeoutMs!=0){
                    apduTimer=new Timer();
                    apduTimeoutTask=new ApduTimeoutTask();
                    apduTimer.schedule(apduTimeoutTask, timeoutMs);
                }
                long start=System.nanoTime();
                try{
                    sendApduImpl(apdu);
                }catch(Throwable e){
                    throw new ScardException("Exception occured during "+apdu,e);
                }
                long end=System.nanoTime();
                boolean log=true;
                if(timeoutMs!=0){
                    apduTimer.cancel();
                    if(apduTimeoutTask.expired){
                        if(null==apduTimeoutScript)
                            throw new ScardException("Timeout for this apdu expired (timeout was "+timeoutMs+"ms)");
                        log=false;
                        apduTimeoutScript.play();
                    }
                }
                if(log){
                    lastApduTotalTime=new Long(end-start);
                    logResponse(apdu);
                }
            }catch(ScardException e){
                log(e);
                throw e;
            }catch(RuntimeException e){
                log(e);
                throw e;
            }
            try {
                apdu.checkResponse();
            } catch (UnexpectedCardResponseException e) {
                logUnexpectedResponse(apdu,e);
                unexpectedCardResponseScript.play();
                //if(null==unexpectedCardResponseResumingScript)
                    throw e;
                //unexpectedCardResponseResumingScript.play();
            }
            localErrorHappened=false;
        }finally{
            if(localErrorHappened){
                errorOccured=true;
                if(null!=apduMiscErrorScript.getScript()){
                    apduMiscErrorScript.play();
                    return;//Trick to resume operations by cancelling any thrown exception
                }
            }
        }
    }
    protected int timeoutMs=0;
    //abstract void setApduTimeoutImpl(int timeoutMs)throws ScardException;
    public void setApduTimeout(int timeoutMs)throws ScardException{
        if(timeoutMs<0)
            throw new IllegalArgumentException("timeoutNs<0");
        this.timeoutMs=timeoutMs;
    }
    public int getApduTimeout(){
        return timeoutMs;
    }
    /*public void sendApdu(Apdu apdu, int timeoutMs)throws ScardException, UnexpectedCardResponseException{
        if(apdu.getExpectedLe()>0x100)
            throw new RuntimeException("le>0x100 not supported yet");
        logCommand(apdu);
        Timer apduTimer=new Timer();
        ApduTimeoutTask apduTimeoutTask=new ApduTimeoutTask();
        apduTimer.schedule(apduTimeoutTask, timeoutMs);
        long start=System.nanoTime();
        sendApduImpl(apdu);
        long end=System.nanoTime();
        apduTimer.cancel();
        lastApduTotalTime=new Long(end-start);
        if(apduTimeoutTask.expired)
            throw new ScardException("Timeout for this apdu expired (timeout was "+timeoutMs+"ms)");
        logResponse(apdu);
        try {
            apdu.checkResponse();
        } catch (UnexpectedCardResponseException e) {
            logUnexpectedResponse(apdu,e);
            throw e;
        }
    }*/
    class ApduTimeoutTask extends TimerTask{
        public boolean expired=false;
        @Override
        public void run() {
            expired=true;
            try {
                forceDisconnection();
            } catch (ScardException ex) {
                Logger.getLogger(GenericTerminal.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    /**
     * As all terminals do not have the same capabilities, some test actions might not be
     * supported by a given terminal. In this case, the terminal should send a
     * <code>UnsupportedOperationException</code>.
     * This function allows to choose the policy in case of such exception
     * @param unsupportedExceptionEnabled if true, exception are raised normally. if false, no <code>unsupportedException</code>
     * will be generated but will be logged with the WARNING level
     */
    public void setUnsupportedExceptionEnabled(boolean unsupportedExceptionEnabled) {
        this.unsupportedExceptionEnabled = unsupportedExceptionEnabled;
    }

    public boolean isUnsupportedExceptionEnabled() {
        return unsupportedExceptionEnabled;
    }

    public void handleUnsupportedOperation(){
        UnsupportedOperationException e=new UnsupportedOperationException();
        if(unsupportedExceptionEnabled)
            throw e;
        logAsWarning(e);
    }

    synchronized public void logEnv(String linePrefix) {
        for(String varName:env.keySet()){
            for(ScardLogHandler h : logHandlers){
                h.logln(ScardLogHandler.LOG_INFO, linePrefix+varName+": "+env.get(varName));
            }
        }
    }

    synchronized public void log(UnexpectedCardResponseException e){
        for(ScardLogHandler h : logHandlers){
            h.log(ScardLogHandler.LOG_ERROR, e.getMessage());
        }
    }
    synchronized public void logAtr(AnswerToReset atr){
        for(ScardLogHandler h : logHandlers){
            h.logAtr(atr);
        }
    }
    synchronized public void logCommand(Apdu apdu){
        for(ScardLogHandler h : logHandlers){
            h.logCommand(apdu);
        }
    }
    synchronized public void logResponse(Apdu apdu){
        for(ScardLogHandler h : logHandlers){
            h.logResponse(apdu);
        }
    }
    synchronized public void logExpectedResponse(Apdu apdu){
        for(ScardLogHandler h : logHandlers){
            h.logExpectedResponse(apdu);
        }
    }
    synchronized public void logUnexpectedResponse(Apdu apdu, UnexpectedCardResponseException e) {
        for(ScardLogHandler h : logHandlers){
            h.logUnexpectedResponse(apdu,e);
        }
    }
    @Override
    synchronized public void log(Throwable e) {
        for(ScardLogHandler h : logHandlers){
            h.log(ScardLogHandler.LOG_ERROR, e);
        }
    }
    @Override
    synchronized public void logAsWarning(Throwable e) {
        for(ScardLogHandler h : logHandlers){
            h.log(ScardLogHandler.LOG_WARNING, e);
        }
    }
    @Override
    synchronized public void log(int eventType, String msg){
        for(ScardLogHandler h : logHandlers){
            h.log(eventType, msg);
        }
    }
    @Override
    synchronized public void logLine(int eventType, String msg){
        for(ScardLogHandler h : logHandlers){
            //h.log(eventType, msg+"\n");
            h.logln(eventType, msg);
        }
    }
    synchronized public void addLogHandler(ScardLogHandler log) {
        log.open();
        logHandlers.add(log);
    }
    synchronized public void closeLog(ScardLogHandler log) throws IOException{
        log.close();
        logHandlers.remove(log);
    }
    public synchronized void removeLog(ScardLogHandler log) throws IOException{
        logHandlers.remove(log);
    }
    @Override
    public synchronized void closeAllLogs() throws IOException{
        for(ScardLogHandler h : logHandlers){
            h.close();
        }       
        logHandlers.clear();
    }
    @Override
    public synchronized void flushAllLogs() throws IOException{
        for(ScardLogHandler h : logHandlers){
            h.flush();
        }       
    }

    @Override
    public synchronized boolean isLoggingOnSystemOut(){
        for(ScardLogHandler log:logHandlers){
            if(log instanceof ScardPrintStreamLogHandler){
                ScardPrintStreamLogHandler scardPrintStreamLogHandler=(ScardPrintStreamLogHandler) log;
                if(System.out==scardPrintStreamLogHandler.getPrintStream())
                    return true;
            }
        }
        return false;
    }

    protected boolean logsEnabled;

    @Override
    public boolean areLogsEnabled() {
        return logsEnabled;
    }

    @Override
    public synchronized void setLogsEnabled(boolean enable) {
        for(ScardLogHandler h : logHandlers){
            h.enabled=enable;
        }
        this.logsEnabled = enable;
    }

    public synchronized Set<ScardLogHandler> getLogs(){
        return logHandlers;
    }

    public boolean isConnected() {
        return connected;
    }

    @Override
    public String toString(){
        return name;
    }

    @Override
    public synchronized void clearLogs() {
        for(ScardLogHandler h : logHandlers){
            h.clear();
        }
    }
    transient long deadTime;

    public long getDeadTime() {
        return deadTime+player.getDeadTime();
    }

    public synchronized void checkConnection() throws ScardException, CardNotPresentException{
        int choice = JOptionPane.YES_OPTION;
        String nl=AStringUtilities.systemNewLine;
        if(false==this.isCardPresent()){
            String msg = "Card not detected !"+nl+nl;
            msg += "Do you want wait for the card and activate it using first proposed protocol ?";
            long start=System.nanoTime();
            choice = JOptionPane.showConfirmDialog(null, msg, "Card not detected", JOptionPane.YES_NO_CANCEL_OPTION);
           
            if (JOptionPane.YES_OPTION == choice) {
                this.waitForCardPresent(0);
                deadTime+=System.nanoTime()-start;
                this.coldConnect();
            }else
                deadTime+=System.nanoTime()-start;
        }else if(false==this.isConnected()){
            String msg = "Card not activated yet !"+nl+nl;
            msg += "Do you want to activate the card using first proposed protocol ?";
            long start=System.nanoTime();
            choice = JOptionPane.showConfirmDialog(null, msg, "Card not activated", JOptionPane.YES_NO_CANCEL_OPTION);
            deadTime+=System.nanoTime()-start;
            if (JOptionPane.YES_OPTION == choice) {
                this.coldConnect();
            }
        }
        if (JOptionPane.CANCEL_OPTION == choice) {
            throw new StopRequestFromUserException("Operation cancelled by user.");
        }
    }
   
    /**
     * Enumeration of attributes of a CardTerminal.
     *
     */
    public static enum State {
  /**
   * any state.
   */
  ALL,
  /**
   * CardTerminals in which a card is present.
   */
  CARD_PRESENT,
  /**
   * CardTerminals in which a card is not present.
   */
  CARD_ABSENT,
  /**
   * CardTerminals for which a card insertion was detected during the
   * latest call to {@linkplain GenericTerminalManager list()}
   * call.
   */
  CARD_INSERTION,
  /**
   * CardTerminals for which a card removal was detected during the
   * latest call to {@linkplain GenericTerminalManager list()}
   * call.
   */
  CARD_REMOVAL,
        /**
   * The state of the terminal is not known yet.
   */
  UNDEFINED,
    }
}
TOP

Related Classes of uk.co.nimp.scard.GenericTerminal$ApduTimeoutTask

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.