/**************************************************************************** * Copyright (C) 2012 ecsec GmbH. * All rights reserved. * Contact: ecsec GmbH (info@ecsec.de) * * This file is part of the Open eCard App. * * GNU General Public License Usage * This file may be used under the terms of the GNU General Public * License version 3.0 as published by the Free Software Foundation * and appearing in the file LICENSE.GPL included in the packaging of * this file. Please review the following information to ensure the * GNU General Public License version 3.0 requirements will be met: * http://www.gnu.org/copyleft/gpl.html. * * Other Usage * Alternatively, this file may be used in accordance with the terms * and conditions contained in a signed written agreement between * you and ecsec GmbH. * ***************************************************************************/ package org.openecard.ifd.scio; import iso.std.iso_iec._24727.tech.schema.ChannelHandleType; import iso.std.iso_iec._24727.tech.schema.GetStatus; import iso.std.iso_iec._24727.tech.schema.GetStatusResponse; import iso.std.iso_iec._24727.tech.schema.IFDStatusType; import iso.std.iso_iec._24727.tech.schema.ResponseType; import iso.std.iso_iec._24727.tech.schema.SignalEvent; import iso.std.iso_iec._24727.tech.schema.SlotStatusType; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.openecard.common.ECardConstants; import org.openecard.common.util.IFDStatusDiff; import org.openecard.ifd.scio.wrapper.SCTerminal; import org.openecard.ifd.scio.wrapper.SCWrapper; import org.openecard.ws.IFDCallback; import org.openecard.ws.marshal.WSClassLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author Tobias Wich <tobias.wich@ecsec.de> */ public class EventListener implements Callable<List<IFDStatusType>> { private static final Logger logger = LoggerFactory.getLogger(EventListener.class); private static final long pollDelay; private static final long pauseDelay; private static long pauseTime = 0; static { String delayStr = IFDProperties.getProperty("org.openecard.ifd.wait.delay"); long delay = 500; if (delayStr != null) { try { delay = Long.parseLong(delayStr); } catch (NumberFormatException ex) { logger.warn("Property 'org.openecard.ifd.wait.delay' contains a malformed number.", ex); } } pollDelay = delay; String pauseStr = IFDProperties.getProperty("org.openecard.ifd.wait.pause"); long pause = 2000; if (delayStr != null) { try { pause = Long.parseLong(pauseStr); } catch (NumberFormatException ex) { logger.warn("Property 'org.openecard.ifd.wait.pause' contains a malformed number.", ex); } } pauseDelay = pause; } private final IFD ifd; private final SCWrapper scWrapper; private final ExecutorService threadPool; private final byte[] ctxHandle; private final boolean withNew; private final ChannelHandleType callback; private final List<IFDStatusType> expectedStatuses; private final long timeout; private final long startTime; private Future<Void> termWatcher; public EventListener(IFD ifd, SCWrapper scWrapper, ExecutorService threadPool, byte[] ctxHandle, long timeout, ChannelHandleType callback, List<IFDStatusType> expectedStatuses, boolean withNew) { this.ifd = ifd; this.scWrapper = scWrapper; this.threadPool = threadPool; this.ctxHandle = ctxHandle; this.timeout = timeout; this.callback = callback; this.expectedStatuses = expectedStatuses; this.withNew = withNew; this.startTime = System.currentTimeMillis(); } /** * Pause wait for events. * The time to pause is set via the property org.openecard.ifd.wait.pause. * If the property is invalid or unset, 2000ms is the default. */ public static synchronized void pause() { pauseTime = System.currentTimeMillis() + pauseDelay; } @Override public List<IFDStatusType> call() throws Exception { try { List<IFDStatusType> result = waitForEvent(); if (isAsync()) { sendResult(result); } return result; } catch (TimeoutException ex) { logger.warn(ex.getMessage(), ex); throw new IFDException(ECardConstants.Minor.IFD.TIMEOUT_ERROR, "Wait timed out."); } catch (Exception ex) { logger.warn(ex.getMessage(), ex); throw ex; // needed to process finally block } finally { // remove async thread from IFD if (isAsync()) { ifd.removeAsnycTerminal(callback.getSessionIdentifier()); } if (termWatcher != null) { termWatcher.cancel(true); } } } private List<IFDStatusType> waitForEvent() throws IFDException, InterruptedException, ExecutionException, TimeoutException { // start watch thread termWatcher = threadPool.submit(new TerminalWatcher()); // get current status and compare it List<IFDStatusType> currentStatus = getCurrentStatus(); IFDStatusDiff diff = new IFDStatusDiff(expectedStatuses); diff.diff(currentStatus, withNew); if (diff.hasChanges()) { termWatcher.cancel(true); return diff.result(); } // no change, wait for watcher to complete long elapsedTime = System.currentTimeMillis() - startTime; long actualTimeout = timeout - elapsedTime; actualTimeout = actualTimeout < 0 ? 1 : actualTimeout; termWatcher.get(actualTimeout, TimeUnit.MILLISECONDS); // get current status and return it currentStatus = getCurrentStatus(); diff = new IFDStatusDiff(expectedStatuses); diff.diff(currentStatus, withNew); return diff.result(); } private List<IFDStatusType> getCurrentStatus() throws IFDException { GetStatus statusReq = new GetStatus(); statusReq.setContextHandle(ctxHandle); GetStatusResponse status = ifd.getStatus(statusReq); if (status.getResult().getResultMajor().equals(ECardConstants.Major.ERROR)) { IFDException ex = new IFDException(status.getResult()); logger.warn(ex.getMessage(), ex); throw ex; } return status.getIFDStatus(); } /** * Send a SOAP call with the given result to the IFDCallback address set when creating the class instance. * * @param result List of result stati. */ private void sendResult(List<IFDStatusType> result) { try { String endpointAddr = callback.getProtocolTerminationPoint(); IFDCallback endpoint = (IFDCallback) WSClassLoader.getClientService("IFDCallback", endpointAddr); SignalEvent sevt = new SignalEvent(); sevt.setContextHandle(ctxHandle); sevt.setSessionIdentifier(callback.getSessionIdentifier()); sevt.getIFDEvent().addAll(result); ResponseType sevtResp = endpoint.signalEvent(sevt); if (sevtResp.getResult().getResultMajor().equals(ECardConstants.Major.ERROR)) { logger.error("SignalEvent returned with an error.\n{}", sevtResp); } } catch (Exception ex) { logger.error(ex.getMessage(), ex); } } public boolean isAsync() { return this.callback != null; } private boolean expectedContains(String ifdName) { Boolean b = expectedGet(ifdName) != null; return b.booleanValue(); } private boolean expectedHasCard(String ifdName) { IFDStatusType s = expectedGet(ifdName); List<SlotStatusType> slots = s.getSlotStatus(); boolean result = false; if (! slots.isEmpty()) { SlotStatusType slot = slots.get(0); result = slot.isCardAvailable(); } return result; } private IFDStatusType expectedGet(String ifdName) { IFDStatusType result = null; for (IFDStatusType s : expectedStatuses) { if (s.getIFDName().equals(ifdName)) { result = s; break; } } return result; } private class TerminalWatcher implements Callable<Void> { @Override public Void call() throws Exception { int pcscErrorCount = 0; // used to break out of the call if pcsc doesn't come back online boolean change = false; while (!change) { // get list of terminals List<SCTerminal> termList = scWrapper.getTerminals(true); // observe status try { // check if there are new or changed terminals List<IFDStatusType> deleted = new LinkedList<IFDStatusType>(expectedStatuses); for (SCTerminal t : termList) { if (expectedContains(t.getName())) { if (t.isCardPresent() != expectedHasCard(t.getName())) { return null; } deleted.remove(expectedGet(t.getName())); } else if (withNew) { return null; } } // check for deleted terminals if (!deleted.isEmpty()) { return null; } // block execution here while(true) { long currentPauseTime; synchronized (EventListener.class) { currentPauseTime = pauseTime; } long now = System.currentTimeMillis(); if (now > currentPauseTime) { break; } Thread.sleep(currentPauseTime - now); } change = scWrapper.waitForChange(pollDelay); // in millis } catch (IFDException ex) { try { // PCSC pooped, try again after a short break pcscErrorCount++; if (pcscErrorCount == 500) { throw ex; } Thread.sleep(1000); } catch (InterruptedException exc) { throw exc; // somebody wants me to quit, so i do it. } } catch (IllegalStateException ex) { try { // no terminals in list triggered this error Thread.sleep(pollDelay); // repeat wait from above } catch (InterruptedException exc) { throw exc; // somebody wants me to quit, so i do it. } } catch (Exception ex) { logger.error(ex.getMessage(), ex); throw ex; } } return null; } } }