SCardTransmit
SCardTransmit() do not work reliably any more on Yosemite. In some cases using 2 PC/SC applications the functions returns SCARD_W_UNPOWERED_CARD (or0x0x80100067
).Interpretation
My interpretation is that Apple tried to implement a card auto power off mechanism. I added this feature in pcsc-lite version 1.6.5 from 2010. See "Card auto power on and off".The card auto power off feature is visible with some smart card readers, like the Gemalto PC Twin (renamed PC USB TR and then renamed IDBridge CT30).
The reader has a LED in it. If the LED blinks then the reader is powered. If the LED is ON then the smart card is powered.
On Mavericks (and the previous versions of Mac OS X) when you insert a card the LED is ON (unless the reader driver is not found) and stays ON until you remove the card.
On Yosemite when you insert a smart card the LED is ON and after 5 seconds the LED blinks. This indicates that the card is powered for 5 seconds and then the card is powered off. Great.
Bug
The problem is that the Apple code is bogus. When 2 PC/SC applications are using the smart smart card at the same time the 5 seconds delay before powering off the card is started when the first application releases the card, even if the second application still has an access to the smart card.After the 5 seconds delay the second application will get a SCARD_W_UNPOWERED_CARD error on the next SCardTransmit() call.
It looks like Apple forgot to power off the card only if NO other PC/SC application is using the card, not just after the first
SCardDisconnect()
.The
sleep(5)
in the sample code is important. This is the delay needed for the PC/SC layer to decide to power off the card. You can also remove this delay and wait before you press enter to continue the first execution.See also
Apple bug report #19764910 "PC/SC: SCardTransmit returns SCARD_W_UNPOWERED_CARD"The bug has been closed by Apple on 8th April 2015 as fixed in 10.10.3.
Sample code
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #ifdef __APPLE__ #include <PCSC/winscard.h> #include <PCSC/wintypes.h> #else #include <winscard.h> #endif int main(int argc, const char * argv[]) { SCARDCONTEXT hContext; LPSTR mszReaders; DWORD err = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &hContext); if (err != SCARD_S_SUCCESS) { printf("ScardEstablishedContext : %08x\n",err); return -1; } DWORD cchReaders = 0; err = SCardListReaders(hContext, "SCard$AllReaders", NULL, &cchReaders); if (err != 0) { printf("ScardListReaders : %08x\n",err); return -1; } mszReaders = calloc(cchReaders, sizeof(char)); if (!mszReaders) { printf("calloc\n"); return -1; } err = SCardListReaders(hContext,"SCard$AllReaders", mszReaders, &cchReaders); if (err != SCARD_S_SUCCESS) { printf("ScardListReaders : %08x\n",err); return -1; } printf("Reader : %s\n", mszReaders); SCARDHANDLE hCard; DWORD dwActiveProtocol; err = SCardConnect(hContext, mszReaders, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &hCard, &dwActiveProtocol); if (err != SCARD_S_SUCCESS) { printf("SCardConnect : %08x\n",err); return -1; } unsigned char cmd[] = {0, 0, 0, 0, 0}; unsigned char resp[255]; DWORD resp_len; SCARD_IO_REQUEST *pci; if (SCARD_PROTOCOL_T0 == dwActiveProtocol) pci = SCARD_PCI_T0; else pci = SCARD_PCI_T1; resp_len = sizeof resp; err = SCardTransmit(hCard, pci, cmd, sizeof cmd, NULL, resp, &resp_len); if (err != SCARD_S_SUCCESS) { printf("ScardTransmit: %08x\n",err); return -1; } printf("SCardTransmit: %0X\n", err); if (1 == argc) { printf("Run the second instance\n"); getchar(); } resp_len = sizeof resp; err = SCardTransmit(hCard, pci, cmd, sizeof cmd, NULL, resp, &resp_len); if (err != SCARD_S_SUCCESS) { printf("ScardTransmit: %08x\n",err); return -1; } printf("SCardTransmit: %0X\n", err); SCardDisconnect(hCard, SCARD_LEAVE_CARD); SCardReleaseContext(hContext); if (argc > 1) { /* The sleeping time is important here */ sleep(5); printf("Continue the first instance\n"); } return 0; }
Result (on Yosemite)
$ CFLAGS="-framework PCSC" make main cc -framework PCSC main.c -o main
Since this sample code needs two programs you need to have two Terminal windows opened.
In the first terminal you run
./main
and in the second terminal you run ./main a
(with an argument).Once the second execution has finished you press enter in the first terminal to continue the execution of the first program.
1st terminal | 2nd terminal |
$ ./main Reader : Gemalto PC Twin Reader SCardTransmit: 0 Run the second instance | |
$ ./main a Reader : Gemalto PC Twin Reader SCardTransmit: 0 SCardTransmit: 0 Continue the first instance | |
ScardTransmit: 80100067 |
Expected result (on Debian)
$ CFLAGS=`pkg-config --cflags libpcsclite` LDFLAGS=`pkg-config --libs libpcsclite` make main cc -pthread -I/usr/include/PCSC -lpcsclite main.c -o main
1st terminal | 2nd terminal |
$ ./main Reader : Gemalto PC Twin Reader SCardTransmit: 0 Run the second instance | |
$ ./main a Reader : Gemalto PC Twin Reader SCardTransmit: 0 SCardTransmit: 0 Continue the first instance | |
ScardTransmit: 0 |
Known workaround
None known.One very bad (and untested) solution would be to create an application that sends an APDU (with no side effect) every 3 or 4 seconds so that the 5 seconds delay never occurs. But that is a very very bad solution. And it may be difficult or impossible to find an APDU that would be safe to send at any time.