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