Hi,

I'm using SCardGetStatusChange under linux, and it's working reasonably
well for me, albeit via the jpcsc java interface.  To demonstrate this,
I've written a "monitor" class which runs in a Java daemon thread - the
code is attached, and is loosely based on Ludovic's suggestion to study
pcsc_scan from pcsc-tools.  The thread first accumulates an array of
available cards/readers, then uses SCardGetStatusChange to keep this
array up to date.

The only issue that I have encountered so far (nb: this code has had one
day's worth of testing), is that an exception is thrown sometimes if a
card is inserted and then removed very quickly.  See my notes in the
code below.  My guess is that this is due to pcsc-lite using a polling
technique because the Omnikey driver that I'm using does not support
hotplug - I'm basing this thesis on what I'm seeing in the pcscd log.
The exception is not a big issue; it is just caught and ignored in the
code below.

BTW: It would be great if the Omnikey guys updated the driver to avoid
the polling - it seems like such a hack, and that particular driver is
intended to support many of their products.

Hope the code helps other Java programmers to get started with
pcsc-lite,

Cheers,

Jason.

package com.logular.pcsc.reader;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.linuxnet.jpcsc.Apdu;
import com.linuxnet.jpcsc.Card;
import com.linuxnet.jpcsc.Context;
import com.linuxnet.jpcsc.PCSC;
import com.linuxnet.jpcsc.State;
import com.utiba.delirium.desfire.Hex;

/**
 */
public class PcscCardMonitor extends Thread {

    /**
     * Used to track readers and cards
     * @author jasong
     */
    static class Reader {
        
        final String readerName;
        boolean cardPresent = false;
        Card card = null;
        final State state;
        
        /**
         * Initialize fields
         */
        public Reader(String readerName) {
            this.readerName = readerName;
            state = new State(readerName);
            System.out.println("Created new reader: " + readerName);
        }
        
        /**
         * Connect to a card if we have not done so already
         */
        private void connect() {
            if (card == null) {
                System.out.println("Connecting to card in reader: " +
readerName);
                // card = pcscContext.Connect(readerName,
PCSC.SHARE_EXCLUSIVE, PCSC.PROTOCOL_T0|PCSC.PROTOCOL_T1);
                card = pcscContext.Connect(readerName);                
            }
        }        
        
        /**
         * Disconnect from any card
         */
        public void disconnect() {
            if (card != null) {
                System.out.println("Disconnecting from card in reader: "
+ readerName);
                try {
                    card.Disconnect();
                } catch (RuntimeException e) {
                    /* It's possible to confuse pcsc-lite by moving the
card through
                     * the field quickly.  This causes "invalid handle"
exceptions
                     * when disconnecting from the card.  
                     * 
                     * Based on the log messages, this may be because
the Omnikey 
                     * driver does not support hotplug, and so pcsc-lite
polls the 
                     * reader every second. 
                     */
                }
                card = null;
            }
        }

        public void handleStatusChange() {
            
            if ((state.dwEventState & PCSC.STATE_CHANGED) != 0) {
                System.out.println("State change detected:\n" +
state.toString());
                // Set the current state to the event state
                
                // Deal with card presence 
                if ((state.dwEventState & PCSC.STATE_PRESENT) == 0) {
                    // No card is present, so disconnect if we
previously had one
                    disconnect();
                }
                else {
                    // A card is present, so connect if we haven't
already
                    connect();
                }
            }
            state.dwCurrentState = state.dwEventState;
        }
    }
    
    
    /**
     * In the case where we are started without a reader being present,
we poll
     * for new readers based on this interval (milliseconds).
     */
    private static final int STARTUP_POLL_INTERVAL = 1000;

    /**
     * Developers aid.  Keep the VM alive for this duration when testing
via main().
     */
    private static final int RUN_DURATION = 30000;    
    
    /**
     * The pcsc context.  Only one of these is allowed to exist at a
time;
     * exceptions will be thrown otherwise.
     */
    private static Context pcscContext;
    
    /**
     * Singleton
     */
    private static PcscCardMonitor instance = null;
    
    /**
     * Map of reader names and Reader
     */
    Map readerMap = new HashMap();
    
    protected PcscCardMonitor() {
        setName("CardMonitor");
        // daemon thread so that we don't keep the JVM up
        setDaemon(true);
    }
    
    /**
     * @return the singleton
     */
    public static PcscCardMonitor instance() {
        if (instance == null) {
            instance = new PcscCardMonitor();
            instance.start();
        }
        return instance;
    }
    
    /**
     * Convenience method to extract all states from our map
     */
    protected State[] getStates() {
        Collection readers = readerMap.values();
        State[] states = new State[readers.size()];
        int index = 0;
        for (Iterator i = readers.iterator(); i.hasNext();) {
            Reader reader = (Reader) i.next();
            states[index++] = reader.state;
        }
        return states;
    }
    
    /**
     * Convenience method to return the number of cached Readers
     */
    protected int readerCount() {
        return readerMap.keySet().size();
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        // Monitor starts lazily - access it to start it.
        PcscCardMonitor.instance();
        // Keep the VM alive
        try {
            Thread.sleep(RUN_DURATION);
        } catch (InterruptedException e) {
        } finally {
        }
    }
    
    /* (non-Javadoc)
     * @see java.lang.Thread#run()
     */
    public void run() {
        try {
            // Would prefer to do this in the constructor so that
clients catch 
            // any exceptions when accessing the singleton, however the
context
            // must be managed by a single thread.  (see pcsc-lite API
doco)
            establishContext();
            
            // Wait forever for at least one reader
            String[] readerNames = new String[]{};
            while (readerNames.length == 0) {
                System.out.println("Waiting for readers...");
                Thread.sleep(STARTUP_POLL_INTERVAL);
                readerNames = pcscContext.ListReaders();
            }
            // Add the readers
            updateReaders(readerNames);
            
            // Process status changes
            while(true) {
                // Immediate return if unaware states are present,
otherwise
                // block until there is an event
                pcscContext.GetStatusChange(PCSC.INFINITE, getStates());
                
                // Status changes are written into the State[] that we
                // supplied to GetStatusChange
                handleStatusChange();
                
                // If the number of readers has changed, then update our
reader map.
                // The State field will be initialize to unaware, so we
will get a
                // status change on the next iteration of this loop.
                readerNames = pcscContext.ListReaders();
                if (readerNames.length != readerCount()) {
                    updateReaders(readerNames);
                }
            }
        } catch (InterruptedException e) {
            System.out.println("InterruptedException in thread: " +
Thread.currentThread().getName());
            // it's OK - the sleep was interrupted
        } finally {
            // This code here for completeness, but does not run for a 
            // daemon thread because the VM is winding things up.
            System.out.println("Cleaning up in thread: " +
Thread.currentThread().getName());
            // Really should disconnect from all cards before releasing
context
            // but this code never gets hit anyway.
            releaseContext();
        }
    }

    /**
     * Iterate over all readers and invoke status change handler
     */
    private void handleStatusChange() {
        Collection readers = readerMap.values();
        for (Iterator i = readers.iterator(); i.hasNext();) {
            Reader reader = (Reader) i.next();
            reader.handleStatusChange();
        }
    }

    /**
     * Add any new readers and remove any that are not specified in
     * the parameter.
     */
    private void updateReaders(String[] newReaderNames) {
        
        // Prepare for collection arithmetic
        List newReaders = new ArrayList();
        for (int i = 0; i < newReaderNames.length; i++) {
            newReaders.add(newReaderNames[i]);
        }
        
        // Find readers to remove
        Collection removals = new HashSet(readerMap.keySet()); 
        removals.removeAll(newReaders);
        // Remove them after invoking dispose to disconnect cards
        for (Iterator iter = removals.iterator(); iter.hasNext();) {
            String readerName = (String) iter.next();
            Reader reader = (Reader)readerMap.get(readerName);
            reader.disconnect();
            readerMap.remove(readerName);
        }
        
        // Find readers to add
        newReaders.removeAll(readerMap.keySet());
        // Create a new Reader, and add same to our map
        // We do not connect here because we need to wait for
        // the State field to be populated
        for (Iterator iter = newReaders.iterator(); iter.hasNext();) {
            String readerName = (String) iter.next();
            Reader reader = new Reader(readerName);
            readerMap.put(readerName, reader);
        }
    }
    
    /**
     * Establish a pcsc-lite context.
     */
    protected void establishContext() {
        if (pcscContext == null) {
            // Instantiate a new context.  This is where we will
            // discover missing shared libraries, etc.
            try {
                System.out.println("Establishing PC/SC context in
thread: " + Thread.currentThread().getName());
                pcscContext = new Context();
                pcscContext.EstablishContext(PCSC.SCOPE_SYSTEM, null,
null);
                System.out.println("Established PC/SC context");
            } catch (RuntimeException e) {
                throw new RuntimeException("Unable to establish PC/SC
context: " + e.getMessage());
            }
        } else {
            throw new UnsupportedOperationException("Only one live
context is currently supported");
        }
    }

    /**
     * Releast the pcsc-lite context.  Note that this must be done in
the same thread
     * that established the context.
     */
    public void releaseContext() {
        if (pcscContext != null) {
            System.out.println("Releasing PC/SC context in thread: " +
Thread.currentThread().getName());
            // This call can block if used incorrectly (i.e. called from
wrong thread)
            pcscContext.ReleaseContext();
            pcscContext = null;
            System.out.println("Context released OK");
        }
    }
}



On Mon, 2007-03-19 at 17:17 -0300, matheus ribeiro wrote:
> Hello list,
> 
> suppose the following situation: between PCSC calls, someone removes a
> card and inserts it again. We have 2 different scenarions here for
> SCardGetStatusChange answer:
> 
> - Linux: no change detected.
> - Windows: change is detected. SCardGetStatusChange sometimes return
> SCARD_STATE_EMPTY and other times return SCARD_STATE_PRESENT. But, the
> change is detected, which is better than no change...
> 
> Also, Windows version returns SCARD_E_TIMEOUT if no change is
> detected, while Linux never returns it.
> 
> It seems Windows keep a event counter at this dwEventState flag. Upper
> bytes increment for each event that occurs (this is not documented,
> but can be seen if you print that variable). With this counter, it
> seems possible to keep track of event changes...
> 
> So, is this a known issue?
> 
> Thanks in advance,
> Matheus
> _______________________________________________
> Muscle mailing list
> [email protected]
> http://lists.drizzle.com/mailman/listinfo/muscle
> 
_______________________________________________
Muscle mailing list
[email protected]
http://lists.drizzle.com/mailman/listinfo/muscle

Reply via email to