Thursday, December 17, 2015

OS X El Capitan missing feature: SCardGetStatusChange() and number of card events

This is part of the series: "OS X El Capitan and smart cards: known bugs".

SCardGetStatusChange() and number of card events

SCardGetStatusChange() does not return the number of card events in the upper word of dwEventState.

This is not a bug since I don't think this feature was every present in the PC/SC from Apple. It is a feature request and not a bug report.

From the Doxygen documentation of pcsc-lite:
dwEventState also contains a number of events in the upper 16 bits (dwEventState & 0xFFFF0000). This number of events is incremented for each card insertion or removal in the specified reader. This can be used to detect a card removal/insertion between two calls to SCardGetStatusChange().
This behaviour is also present in PC/SC on Windows.

It is the only way to know that the card has been removed from a reader and inserted again while no call to SCardGetStatusChange() was running. Potentially the inserted card is not the same as the removed card so the PC/SC application may have to build a new applicative context for the new card.

See also

Apple bug report #23937633 "PC/SC SCardGetStatusChange() and number of card events"

Sample code

Using the Python PC/SC wrapper PySCard to have a shorter code.

#! /usr/bin/env python

from smartcard.scard import *
from smartcard.pcsc.PCSCExceptions import *


def parseEventState(eventstate):
    stateList = list()
    states = {SCARD_STATE_IGNORE: "Ignore",
              SCARD_STATE_CHANGED: "Changed",
              SCARD_STATE_UNKNOWN: "Unknown",
              SCARD_STATE_UNAVAILABLE: "Unavailable",
              SCARD_STATE_EMPTY: "Empty",
              SCARD_STATE_PRESENT: "Present",
              SCARD_STATE_ATRMATCH: "ATR match",
              SCARD_STATE_EXCLUSIVE: "Exclusive",
              SCARD_STATE_INUSE: "In use",
              SCARD_STATE_MUTE: "Mute"}
    for state in states:
        if eventstate & state:
            stateList.append(states[state])
    return stateList

hresult, hcontext = SCardEstablishContext(SCARD_SCOPE_USER)
if hresult != SCARD_S_SUCCESS:
    raise EstablishContextException(hresult)

hresult, readers = SCardListReaders(hcontext, [])
if hresult != SCARD_S_SUCCESS:
    raise ListReadersException(hresult)
print 'PC/SC Readers:', readers

readerstates = {}
for reader in readers:
    readerstates[reader] = (reader, SCARD_STATE_UNAWARE)
hresult, newstates = SCardGetStatusChange(hcontext, 0, readerstates.values())
if hresult != SCARD_S_SUCCESS:
    raise BaseSCardException(hresult)
print newstates
for readerState in newstates:
    readername, eventstate, atr = readerState
    readerstates[readername] = (readername, eventstate)

print "Move the smart card"

ok = True
while ok:
    try:
        hresult, newstates = SCardGetStatusChange(hcontext, INFINITE, newstates)
        if hresult != SCARD_S_SUCCESS and hresult != SCARD_E_TIMEOUT:
            raise BaseSCardException(hresult)
    except KeyboardInterrupt:
        ok = False
    for readerState in newstates:
        readername, eventstate, atr = readerState
        states = parseEventState(eventstate)
        print states, hex(eventstate)

hresult = SCardReleaseContext(hcontext)
print "SCardReleaseContext()", SCardGetErrorMessage(hresult)
if hresult != SCARD_S_SUCCESS:
    raise ReleaseContextException(hresult)

Result (on El Capitan)

$ ./SCardGetStatusChange.py
PC/SC Readers: ['Gemalto PC Twin Reader']
[('Gemalto PC Twin Reader', 18, [])]
Move the smart card
['Changed', 'Present'] 0x22
['Empty', 'Changed'] 0x12
['Changed', 'Present'] 0x22
['Empty', 'Changed'] 0x12
^C['Empty', 'Changed'] 0x12
SCardReleaseContext() Command successful.

For the tests I insert and remove the card 2 times.

Then press Control-C and insert the card to quit the program.

Expected result (on Debian)

$ ./SCardGetStatusChange.py 
PC/SC Readers: ['Gemalto PC Twin Reader 00 00']
[('Gemalto PC Twin Reader 00 00', 18, [])]
Move the smart card
['Changed', 'Present'] 0x10022
['Empty', 'Changed'] 0x20012
['Changed', 'Present'] 0x30022
['Empty', 'Changed'] 0x40012
^C['Empty', 'Changed'] 0x40012
SCardReleaseContext() Command successful.

The change is the value eventstate displayed (in hexadecimal).

On Debian the high word (upper 16 bits) takes the values 1, 2, 3 and 4.

If the value change from 0x10022 to 0x30022, the reader state is the same (SCARD_STATE_CHANGED and SCARD_STATE_PRESENT) but you know that the card has been removed and inserted.

Known workaround

None known.