Ejemplo n.º 1
0
 /**
  * Used to get the name of the readers connected.
  *
  * @return A list of the names.
  */
 public ArrayList<String> getReadersName() {
   ArrayList<String> list = new ArrayList<>();
   try {
     for (CardTerminal terminal : terminalFactory.terminals().list()) list.add(terminal.getName());
   } catch (CardException exception) {
   }
   return list;
 }
Ejemplo n.º 2
0
  private synchronized boolean activateTerminal() {
    Interfacer.getLogger().log("ACTIVATE TERMINAL");
    // select which terminal to use
    if (_cardTerminal != null) _activeCardTerminal = _cardTerminal;
    else {
      while (_activeCardTerminal == null) {
        try {
          for (CardTerminal cardTerminalLoop : _cardTerminals) {
            if (cardTerminalLoop.isCardPresent()
                && !cardTerminalLoop.getName().toUpperCase().contains("EMULATOR")) {
              _activeCardTerminal = cardTerminalLoop;
              break;
            }
          }
          if (_activeCardTerminal == null) {
            Interfacer.getLogger().log("INSERT CARD (s)");
            Thread.sleep(1000);
          }
        } catch (Exception e) {
        }
      }
    }
    Interfacer.getLogger().log("ACTIVE TERMINAL: " + _activeCardTerminal.getName());
    try {
      // wait for a card to be put in the terminal
      while (!_activeCardTerminal.isCardPresent()
          && !_activeCardTerminal.getName().toUpperCase().contains("EMULATOR")) {
        Interfacer.getLogger().log("INSERT CARD (s)");
        Thread.sleep(1000);
      }

      if (_activePassportService == null) {
        _activeCardService = new TerminalCardService(_activeCardTerminal);
        _activePassportService = new PassportService(_activeCardService);
        _activePassportService.open();
      }
      Interfacer.getLogger().log("CARD INSERTED AT: " + _activeCardTerminal.getName());
      return true;
    } catch (Exception e) {
      e.printStackTrace();
    }

    // something went wrong if we reached this point, clear the selected terminal
    Interfacer.getLogger().log("NO TERMINAL COULD BE ACTIVATED");
    return false;
  }
Ejemplo n.º 3
0
 /**
  * What is doing the thread.
  *
  * <p>Will check if there is a reader available containing the wanted name (will call {@link
  * TerminalListener#cardReaderRemoved()} ()} if a listener is removed or added). If it is the case
  * it will wait for a card placed, call {@link TerminalListener#cardAdded(RFIDCard)}, wait for the
  * card to be removed then call {@link TerminalListener#cardRemoved()}
  */
 @Override
 public void run() {
   while (!Thread.interrupted()) {
     if (terminalFactory == null)
       try {
         Thread.sleep(500);
       } catch (InterruptedException e) {
       }
     boolean lastPresent = this.isPresent;
     try {
       final CardTerminals terminalList = terminalFactory.terminals();
       CardTerminal cardTerminal = null;
       try {
         for (CardTerminal terminal : terminalList.list())
           if (terminal.getName().equals(this.terminalName)) {
             cardTerminal = terminal;
             this.isPresent = true;
             break;
           }
       } catch (CardException exception) {
       }
       if (cardTerminal == null) this.isPresent = false;
       if (this.isPresent != lastPresent) {
         if (this.isPresent)
           logger.log(Level.INFO, "Starting listening terminal " + cardTerminal.getName());
         else logger.log(Level.INFO, "Stopped listening");
         for (TerminalListener listener : this.listenersTerminal)
           if (this.isPresent) listener.cardReaderAdded();
           else listener.cardReaderRemoved();
       }
       if (!this.isPresent) continue;
       logger.log(Level.INFO, "Waiting for card...");
       cardTerminal.waitForCardPresent(0);
       logger.log(Level.INFO, "Card detected");
       this.lastCard = getCardInfos(cardTerminal.connect("*"));
       for (TerminalListener listener : this.listenersTerminal) listener.cardAdded(this.lastCard);
       cardTerminal.waitForCardAbsent(0);
       this.lastCard = null;
       logger.log(Level.INFO, "Card removed");
       for (TerminalListener listener : this.listenersTerminal) listener.cardRemoved();
     } catch (Exception exception) {
       logger.log(Level.WARNING, "", exception);
     }
   }
 }
Ejemplo n.º 4
0
 public CardSession createCardSession(String rdr) {
   CardTerminal term = null;
   synchronized (terms) {
     for (CardTerminal t : terms) {
       if (t.getName().equals(rdr)) term = t;
     }
   }
   return new CardSessionImpl(term, rdr);
 }
Ejemplo n.º 5
0
    @Override
    public List<TerminalState> start() throws SCIOException {
      logger.trace("Entering start().");
      if (pendingEvents != null) {
        throw new IllegalStateException(
            "Trying to initialize already initialized watcher instance.");
      }
      pendingEvents = new LinkedList<>();
      terminals = new HashSet<>();
      cardPresent = new HashSet<>();

      try {
        // call wait for change and directly afterwards get current list of cards
        // with a bit of luck no change has happened in between and the list is coherent
        own.terminals.waitForChange(1);
        List<CardTerminal> javaTerminals = own.terminals.list();
        ArrayList<TerminalState> result = new ArrayList<>(javaTerminals.size());
        // fill sets according to state of the terminals
        logger.debug("Detecting initial terminal status.");
        for (CardTerminal next : javaTerminals) {
          String name = next.getName();
          boolean cardInserted = next.isCardPresent();
          logger.debug("Terminal='{}' cardPresent={}", name, cardInserted);
          terminals.add(name);
          if (cardInserted) {
            cardPresent.add(name);
            result.add(new TerminalState(name, true));
          } else {
            result.add(new TerminalState(name, false));
          }
        }
        // return list of our terminals
        logger.trace("Leaving start() with {} states.", result.size());
        return Collections.unmodifiableList(result);
      } catch (CardException ex) {
        if (getCode(ex) == SCIOErrorCode.SCARD_E_NO_READERS_AVAILABLE) {
          logger.debug("No reader available exception.");
          return Collections.emptyList();
        } else if (getCode(ex) == SCIOErrorCode.SCARD_E_NO_SERVICE) {
          logger.debug("No service available exception, reloading PCSC and returning empty list.");
          parent.reloadFactory();
          own.loadTerminals();
          return Collections.emptyList();
        }
        String msg = "Failed to retrieve status from the PCSC system.";
        logger.error(msg, ex);
        throw new SCIOException(msg, getCode(ex), ex);
      } catch (IllegalStateException ex) {
        logger.debug("No reader available exception.");
        return Collections.emptyList();
      }
    }
Ejemplo n.º 6
0
 public String toString() {
   StringBuffer result = new StringBuffer();
   List<CardTerminal> terminals = manager.getTerminals();
   for (CardTerminal terminal : terminals) {
     result.append("[" + (manager.isPolling(terminal) ? "X" : " ") + "] ");
     result.append(terminal.getName());
     result.append("\n");
   }
   result.append("\n");
   result.append("Service = " + service);
   result.append("\n");
   return result.toString();
 }
Ejemplo n.º 7
0
  /**
   * Create a new instance of PassportLink. All CardTerminals will be listed. Upon a request all
   * terminals will be checked for the presence of a passport. The first terminal to respond will be
   * chosen for as long as the passport is present.
   */
  public PassportLink() {
    Security.insertProviderAt(PROVIDER, 4);

    _cardManager = CardManager.getInstance();
    _cardTerminals = _cardManager.getTerminals();
    _activeCardTerminal = null;
    _cardTerminal = null;
    _activeCardService = null;
    _activePassportService = null;

    Vector<CardTerminal> terminalsToRemove = new Vector<CardTerminal>();
    for (CardTerminal cardTerminalLoop : _cardTerminals) {
      if (cardTerminalLoop.getName().toUpperCase().contains("EMULATOR")) {
        Interfacer.getLogger().log("Removing emulator terminal.");
        terminalsToRemove.add(cardTerminalLoop);
      }
      Interfacer.getLogger().log("terminal name: " + cardTerminalLoop.getName());
    }

    for (CardTerminal removeTerminal : terminalsToRemove)
      _cardManager.getTerminals().remove(removeTerminal);
  }
 /**
  * openChannel
  *
  * @param applet
  * @return Channel
  * @throws Exception
  */
 private CardChannel openChannel(AppletModel applet) throws Exception {
   if (channel != null) {
     try {
       channel.close();
     } catch (Exception e) {
       //
     }
     channel = null;
   }
   if (card != null) {
     try {
       card.disconnect(true);
     } catch (Exception e) {
       //
     }
     card = null;
   }
   TerminalFactory factory = TerminalFactory.getDefault();
   CardTerminals cardterminals = factory.terminals();
   try {
     List<CardTerminal> terminals = cardterminals.list();
     System.out.println("Terminals: " + terminals);
     for (CardTerminal terminal : terminals) {
       terminal.waitForCardPresent(1000);
       if (terminal.isCardPresent()) {
         System.out.println(terminal.getName() + ": Card present!");
         card = terminal.connect("*");
         channel = card.getBasicChannel();
         return channel;
       }
     }
     throw new WolfException(MSG_READER_TIME_OUT);
   } catch (Exception e) {
     throw e;
   }
 }
Ejemplo n.º 9
0
  private static void work(CardTerminal reader, OptionSet args) throws CardException {
    if (!reader.isCardPresent()) {
      System.out.println("No card in " + reader.getName());
      return;
    }

    FileOutputStream o = null;
    if (args.has(OPT_DUMP)) {
      try {
        o = new FileOutputStream((File) args.valueOf(OPT_DUMP));
      } catch (FileNotFoundException e) {
        System.err.println("Can not dump to " + args.valueOf(OPT_DUMP));
      }
    }
    reader = LoggingCardTerminal.getInstance(reader, o);
    // This allows to override the protocol for RemoteTerminal as well.
    final String protocol;
    if (args.has(OPT_T0)) {
      protocol = "T=0";
    } else if (args.has(OPT_T1)) {
      protocol = "T=1";
    } else {
      protocol = "*";
    }
    if (args.has(CMD_APDU)) {

      Card c = null;
      try {
        c = reader.connect(protocol);

        if (args.has(CMD_APDU)) {
          for (Object s : args.valuesOf(CMD_APDU)) {
            CommandAPDU a = new CommandAPDU(HexUtils.stringToBin((String) s));
            ResponseAPDU r = c.getBasicChannel().transmit(a);
            if (args.has(OPT_ERROR) && r.getSW() != 0x9000) {
              System.out.println(
                  "Card returned " + String.format("%04X", r.getSW()) + ", exiting!");
              return;
            }
          }
        }
      } catch (CardException e) {
        if (TerminalManager.getExceptionMessage(e) != null) {
          System.out.println("PC/SC failure: " + TerminalManager.getExceptionMessage(e));
        } else {
          throw e;
        }
      } finally {
        if (c != null) {
          c.disconnect(true);
        }
      }
    } else if (args.has(OPT_CONNECT)) {
      String remote = (String) args.valueOf(OPT_CONNECT);
      JSONMessagePipe transport = null;

      try {
        if (remote.startsWith("http://") || remote.startsWith("https://")) {
          if (args.has(OPT_PINNED)) {
            transport =
                HTTPTransport.open(
                    new URL(remote), certFromPEM(((File) args.valueOf(OPT_PINNED)).getPath()));
          } else {
            transport = HTTPTransport.open(new URL(remote), null);
          }
        } else {
          transport = SocketTransport.connect(string2socket(remote), null);
        }

        // Connect the transport and the terminal
        CmdlineRemoteTerminal c = new CmdlineRemoteTerminal(transport, reader);
        c.forceProtocol(protocol);
        // Run
        c.run();
      } catch (IOException e) {
        System.err.println("Communication error: " + e.getMessage());
      } finally {
        if (transport != null) transport.close();
      }
    }
  }
Ejemplo n.º 10
0
  public static void main(String[] argv) throws Exception {
    OptionSet args = parseOptions(argv);

    if (args.has(OPT_VERBOSE)) {
      verbose = true;
      // Set up slf4j simple in a way that pleases us
      System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug");
      System.setProperty("org.slf4j.simpleLogger.showThreadName", "true");
      System.setProperty("org.slf4j.simpleLogger.showShortLogName", "true");
      System.setProperty("org.slf4j.simpleLogger.levelInBrackets", "true");
    } else {
      System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "warn");
    }

    if (args.has(OPT_VERSION)) {
      String version = "apdu4j " + getVersion(SCTool.class);
      // Append host information
      version += "\nRunning on " + System.getProperty("os.name");
      version += " " + System.getProperty("os.version");
      version += " " + System.getProperty("os.arch");
      version += ", Java " + System.getProperty("java.version");
      version += " by " + System.getProperty("java.vendor");
      System.out.println(version);
    }
    if (args.has(OPT_TEST_SERVER)) {
      // TODO: have the possibility to run SocketServer as well?
      RemoteTerminalServer srv = new RemoteTerminalServer(TestServer.class);
      srv.start(string2socket((String) args.valueOf(OPT_TEST_SERVER)));
      System.console().readLine("Press enter to stop\n");
      srv.stop(1);
      System.exit(0);
    }

    // List TerminalFactory providers
    if (args.has(OPT_PROVIDERS)) {
      Provider providers[] = Security.getProviders("TerminalFactory.PC/SC");
      if (providers != null) {
        System.out.println("Existing TerminalFactory providers:");
        for (Provider p : providers) {
          System.out.println(p.getName());
        }
      }
    }

    // Fix properties on non-windows platforms
    TerminalManager.fixPlatformPaths();

    // Only applies to SunPCSC
    if (args.has(OPT_NO_GET_RESPONSE)) {
      System.setProperty("sun.security.smartcardio.t0GetResponse", "false");
      System.setProperty("sun.security.smartcardio.t1GetResponse", "false");
    }

    // Override PC/SC library path
    if (args.has(OPT_LIB)) {
      System.setProperty("sun.security.smartcardio.library", (String) args.valueOf(OPT_LIB));
    }

    TerminalFactory tf = null;
    CardTerminals terminals = null;

    try {
      // Get a terminal factory
      if (args.has(OPT_PROVIDER)) {
        String pn = (String) args.valueOf(OPT_PROVIDER);
        String pt = (String) args.valueOf(OPT_PROVIDER_TYPE);
        tf = loadFactory(pn, pt);
      } else if (args.has(OPT_SUN)) {
        tf = loadFactory(SUN_CLASS, null);
      } else if (args.has(OPT_JNA)) {
        tf = loadFactory(JNA_CLASS, null);
      } else {
        tf = TerminalFactory.getDefault();
      }

      if (verbose) {
        System.out.println(
            "# Using " + tf.getProvider().getClass().getCanonicalName() + " - " + tf.getProvider());
        if (System.getProperty(TerminalManager.lib_prop) != null) {
          System.out.println(
              "# " + TerminalManager.lib_prop + "=" + System.getProperty(TerminalManager.lib_prop));
        }
      }
      // Get all terminals
      terminals = tf.terminals();
    } catch (Exception e) {
      // XXX: we catch generic Exception here to avoid importing JNA.
      // Try to get a meaningful message
      String msg = TerminalManager.getExceptionMessage(e);
      if (msg == null) msg = e.getMessage();
      System.out.println("No readers: " + msg);
      System.exit(1);
    }

    // Terminals to work on
    List<CardTerminal> do_readers = new ArrayList<CardTerminal>();

    try {
      // List Terminals
      if (args.has(CMD_LIST)) {
        List<CardTerminal> terms = terminals.list();
        if (verbose) {
          System.out.println(
              "# Found " + terms.size() + " terminal" + (terms.size() == 1 ? "" : "s"));
        }
        if (terms.size() == 0) {
          System.err.println("No readers found");
          System.exit(1);
        }
        for (CardTerminal t : terms) {
          String vmd = " ";
          try (PinPadTerminal pp = new PinPadTerminal(t)) {
            pp.probe();
            // Verify, Modify, Display
            if (verbose) {
              vmd += "[";
              vmd += pp.canVerify() ? "V" : " ";
              vmd += pp.canModify() ? "M" : " ";
              vmd += pp.hasDisplay() ? "D" : " ";
              vmd += "] ";
            }
          } catch (CardException e) {
            if (verbose) {
              System.err.println("Could not probe PinPad: " + e.getMessage());
            }
          }

          System.out.println((t.isCardPresent() ? "[*]" : "[ ]") + vmd + t.getName());

          if (args.has(OPT_VERBOSE) && t.isCardPresent()) {
            Card c = t.connect("DIRECT");
            String atr = HexUtils.encodeHexString(c.getATR().getBytes()).toUpperCase();
            c.disconnect(false);
            System.out.println("          " + atr);
            if (args.has(OPT_WEB)) {
              String url = "http://smartcard-atr.appspot.com/parse?ATR=" + atr;
              if (Desktop.isDesktopSupported()) {
                Desktop.getDesktop().browse(new URI(url + "&from=apdu4j"));
              } else {
                System.out.println("          " + url);
              }
            }
          }
        }
      }

      // Select terminals to work on
      if (args.has(OPT_READER)) {
        String reader = (String) args.valueOf(OPT_READER);
        CardTerminal t = terminals.getTerminal(reader);
        if (t == null) {
          System.err.println("Reader \"" + reader + "\" not found.");
          System.exit(1);
        }
        do_readers = Arrays.asList(t);
      } else {
        do_readers = terminals.list(State.CARD_PRESENT);
        if (do_readers.size() > 1 && !args.hasArgument(OPT_ALL)) {
          System.err.println("More than one reader with a card found.");
          System.err.println("Run with --" + OPT_ALL + " to work with all found cards");
          System.exit(1);
        } else if (do_readers.size() == 0 && !args.has(CMD_LIST)) {
          // But if there is a single reader, wait for a card insertion
          List<CardTerminal> empty = terminals.list(State.CARD_ABSENT);
          if (empty.size() == 1 && args.has(OPT_WAIT)) {
            CardTerminal rdr = empty.get(0);
            System.out.println("Please enter a card into " + rdr.getName());
            if (!empty.get(0).waitForCardPresent(30000)) {
              System.out.println("Timeout.");
            } else {
              do_readers = Arrays.asList(rdr);
            }
          } else {
            System.err.println("No reader with a card found!");
            System.exit(1);
          }
        }
      }

    } catch (CardException e) {
      System.out.println("Could not list readers: " + TerminalManager.getExceptionMessage(e));
      e.printStackTrace();
    }

    for (CardTerminal t : do_readers) {
      work(t, args);
    }
  }
Ejemplo n.º 11
0
 /**
  * Create a new instance of PassportLink specifying the name of the CardTerminal to use.
  *
  * @param terminalToUse Name of CardTerminal to use.
  */
 public PassportLink(String terminalToUse) {
   this();
   for (CardTerminal cardTerminalLoop : _cardTerminals) {
     if (cardTerminalLoop.getName().equals(terminalToUse)) _cardTerminal = cardTerminalLoop;
   }
 }
Ejemplo n.º 12
0
    /**
     * Wait for events in the system. The SmartcardIO wait function only reacts on card events, new
     * and removed terminals go unseen. in order to fix this, we wait only a short time and check
     * the terminal list periodically.
     *
     * @param timeout Timeout values as in {@link #waitForChange(long)}.
     * @return The first value is the changed flag . It is {@code true} if a change the terminals
     *     happened, {@code false} if a timeout occurred. <br>
     *     The second value is the error flag. It is {@code true} if an error was used to indicate
     *     that no terminals are connected, {@code false} otherwise.
     * @throws CardException Thrown if any error related to the SmartcardIO occured.
     * @throws SCIOException Thrown if the thread was interrupted. Contains the code {@link
     *     SCIOErrorCode#SCARD_E_SERVICE_STOPPED}.
     */
    private Pair<Boolean, Boolean> internalWait(long timeout) throws CardException, SCIOException {
      // the SmartcardIO wait function only reacts on card events, new and removed terminals go
      // unseen
      // to fix this, we wait only a short time and check the terminal list periodically
      if (timeout < 0) {
        throw new IllegalArgumentException("Negative timeout value given.");
      } else if (timeout == 0) {
        timeout = Long.MAX_VALUE;
      }

      while (true) {
        if (timeout == 0) {
          // waited for all time and nothing happened
          return new Pair<>(false, false);
        }
        // calculate next wait slice
        long waitTime;
        if (timeout < WAIT_DELTA) {
          waitTime = timeout;
          timeout = 0;
        } else {
          timeout = timeout - WAIT_DELTA;
          waitTime = WAIT_DELTA;
        }

        try {
          // check if there is something new on the card side
          // due to the wait call blocking every other smartcard operation, we only wait for the
          // actual events
          // very shortly and sleep for the rest of the time
          boolean change = own.terminals.waitForChange(1);
          if (change) {
            return new Pair<>(true, false);
          }
          sleep(waitTime);
          // try again after sleeping
          change = own.terminals.waitForChange(1);
          if (change) {
            return new Pair<>(true, false);
          }
        } catch (CardException ex) {
          switch (getCode(ex)) {
            case SCARD_E_NO_SERVICE:
              logger.debug("No service available exception, reloading PCSC.");
              parent.reloadFactory();
              own.loadTerminals();
            case SCARD_E_NO_READERS_AVAILABLE:
              // send events that everything is removed if there are any terminals connected right
              // now
              if (!terminals.isEmpty()) {
                return new Pair<>(true, true);
              } else {
                logger.debug("Waiting for PCSC system to become available again.");
                // if nothing changed, wait a bit and try again
                sleep(waitTime);
                continue;
              }
            default:
              throw ex;
          }
        } catch (IllegalStateException ex) {
          // send events that everything is removed if there are any terminals connected right now
          if (!terminals.isEmpty()) {
            return new Pair<>(true, true);
          } else {
            logger.debug("Waiting for PCSC system to become available again.");
            // if nothing changed, wait a bit and try again
            sleep(waitTime);
            continue;
          }
        }

        // check if there is something new on the terminal side
        ArrayList<CardTerminal> currentTerms = new ArrayList<>(own.terminals.list());
        if (currentTerms.size() != terminals.size()) {
          return new Pair<>(true, false);
        }
        // same size, but still compare terminal names
        HashSet<String> newTermNames = new HashSet<>();
        for (CardTerminal next : currentTerms) {
          newTermNames.add(next.getName());
        }
        int sizeBefore = newTermNames.size();
        if (sizeBefore != terminals.size()) {
          return new Pair<>(false, false);
        }
        newTermNames.addAll(terminals);
        int sizeAfter = newTermNames.size();
        if (sizeBefore != sizeAfter) {
          return new Pair<>(false, false);
        }
      }
    }
Ejemplo n.º 13
0
    @Override
    public StateChangeEvent waitForChange(long timeout) throws SCIOException {
      logger.trace("Entering waitForChange().");
      if (pendingEvents == null) {
        throw new IllegalStateException("Calling wait on uninitialized watcher instance.");
      }

      // try to return any present events first
      StateChangeEvent nextEvent = pendingEvents.poll();
      if (nextEvent != null) {
        logger.trace("Leaving waitForChange() with queued event.");
        return nextEvent;
      } else {
        Pair<Boolean, Boolean> waitResult;
        try {
          waitResult = internalWait(timeout);
        } catch (CardException ex) {
          String msg = "Error while waiting for a state change in the terminals.";
          logger.error(msg, ex);
          throw new SCIOException(msg, getCode(ex), ex);
        }
        boolean changed = waitResult.p1;
        boolean error = waitResult.p2;

        if (!changed) {
          logger.trace("Leaving waitForChange() with no event.");
          return new StateChangeEvent();
        } else {
          // something has changed, retrieve actual terminals from the system and see what has
          // changed
          Collection<String> newTerminals = new HashSet<>();
          Collection<String> newCardPresent = new HashSet<>();
          // only ask for terminals if there is no error
          if (!error) {
            try {
              List<CardTerminal> newStates = own.terminals.list();
              for (CardTerminal next : newStates) {
                String name = next.getName();
                newTerminals.add(name);
                if (next.isCardPresent()) {
                  newCardPresent.add(name);
                }
              }
            } catch (CardException ex) {
              String msg = "Failed to retrieve status of the observed terminals.";
              logger.error(msg, ex);
              throw new SCIOException(msg, getCode(ex), ex);
            }
          }

          // calculate what has actually happened
          // removed cards
          Collection<String> cardRemoved = subtract(cardPresent, newCardPresent);
          Collection<StateChangeEvent> crEvents = createEvents(EventType.CARD_REMOVED, cardRemoved);
          // removed terminals
          Collection<String> termRemoved = subtract(terminals, newTerminals);
          Collection<StateChangeEvent> trEvents =
              createEvents(EventType.TERMINAL_REMOVED, termRemoved);
          // added terminals
          Collection<String> termAdded = subtract(newTerminals, terminals);
          Collection<StateChangeEvent> taEvents = createEvents(EventType.TERMINAL_ADDED, termAdded);
          // added cards
          Collection<String> cardAdded = subtract(newCardPresent, cardPresent);
          Collection<StateChangeEvent> caEvents = createEvents(EventType.CARD_INSERTED, cardAdded);

          // update internal status with the calculated state
          terminals = newTerminals;
          cardPresent = newCardPresent;
          pendingEvents.addAll(crEvents);
          pendingEvents.addAll(trEvents);
          pendingEvents.addAll(taEvents);
          pendingEvents.addAll(caEvents);
          // use remove so we get an exception when no event has been recorded
          // this would mean our algorithm is corrupt
          logger.trace("Leaving waitForChange() with fresh event.");
          return pendingEvents.remove();
        }
      }
    }