PCSC sample in C

Here is the PCSC sample in C language I promised in PC/SC sample in different languages.

C Source code


#ifdef WIN32
#undef UNICODE
#endif

#include <stdio.h>
#include <stdlib.h>

#ifdef __APPLE__
#include <PCSC/winscard.h>
#include <PCSC/wintypes.h>
#else
#include <winscard.h>
#endif

#ifdef WIN32
static char *pcsc_stringify_error(LONG rv)
{
 static char out[20];
 sprintf_s(out, sizeof(out), "0x%08X", rv);

 return out;
}
#endif

#define CHECK(f, rv) \
 if (SCARD_S_SUCCESS != rv) \
 { \
  printf(f ": %s\n", pcsc_stringify_error(rv)); \
  return -1; \
 }

int main(void)
{
 LONG rv;

 SCARDCONTEXT hContext;
 LPTSTR mszReaders;
 SCARDHANDLE hCard;
 DWORD dwReaders, dwActiveProtocol, dwRecvLength;

 SCARD_IO_REQUEST pioSendPci;
 BYTE pbRecvBuffer[258];
 BYTE cmd1[] = { 0x00, 0xA4, 0x04, 0x00, 0x0A, 0xA0,
  0x00, 0x00, 0x00, 0x62, 0x03, 0x01, 0x0C, 0x06, 0x01 };
 BYTE cmd2[] = { 0x00, 0x00, 0x00, 0x00 };

 unsigned int i;

 rv = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &hContext);
 CHECK("SCardEstablishContext", rv)

#ifdef SCARD_AUTOALLOCATE
 dwReaders = SCARD_AUTOALLOCATE;

 rv = SCardListReaders(hContext, NULL, (LPTSTR)&mszReaders, &dwReaders);
 CHECK("SCardListReaders", rv)
#else
 rv = SCardListReaders(hContext, NULL, NULL, &dwReaders);
 CHECK("SCardListReaders", rv)

 mszReaders = calloc(dwReaders, sizeof(char));
 rv = SCardListReaders(hContext, NULL, mszReaders, &dwReaders);
 CHECK("SCardListReaders", rv)
#endif
 printf("reader name: %s\n", mszReaders);

 rv = SCardConnect(hContext, mszReaders, SCARD_SHARE_SHARED,
  SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &hCard, &dwActiveProtocol);
 CHECK("SCardConnect", rv)

 switch(dwActiveProtocol)
 {
  case SCARD_PROTOCOL_T0:
   pioSendPci = *SCARD_PCI_T0;
   break;

  case SCARD_PROTOCOL_T1:
   pioSendPci = *SCARD_PCI_T1;
   break;
 }
 dwRecvLength = sizeof(pbRecvBuffer);
 rv = SCardTransmit(hCard, &pioSendPci, cmd1, sizeof(cmd1),
  NULL, pbRecvBuffer, &dwRecvLength);
 CHECK("SCardTransmit", rv)

 printf("response: ");
 for(i=0; i<dwRecvLength; i++)
  printf("%02X ", pbRecvBuffer[i]);
 printf("\n");

 dwRecvLength = sizeof(pbRecvBuffer);
 rv = SCardTransmit(hCard, &pioSendPci, cmd2, sizeof(cmd2),
  NULL, pbRecvBuffer, &dwRecvLength);
 CHECK("SCardTransmit", rv)

 printf("response: ");
 for(i=0; i<dwRecvLength; i++)
  printf("%02X ", pbRecvBuffer[i]);
 printf("\n");

 rv = SCardDisconnect(hCard, SCARD_LEAVE_CARD);
 CHECK("SCardDisconnect", rv)

#ifdef SCARD_AUTOALLOCATE
 rv = SCardFreeMemory(hContext, mszReaders);
 CHECK("SCardFreeMemory", rv)

#else
 free(mszReaders);
#endif

 rv = SCardReleaseContext(hContext);

 CHECK("SCardReleaseContext", rv)

 return 0;
}

Makefile

# Linux
PCSC_CFLAGS := $(shell pkg-config --cflags libpcsclite)
LDFLAGS := $(shell pkg-config --libs libpcsclite)

# Mac OS X
#PCSC_CFLAGS := -framework PCSC

CFLAGS += $(PCSC_CFLAGS)

sample: sample.c

clean:
 rm -f sample

Outputs

The output is "Hello world!" which is in hex:

$ echo -n 'Hello world!' | xxd -g 1
0000000: 48 65 6c 6c 6f 20 77 6f 72 6c 64 21   Hello world!

GNU/Linux and Mac OS X

reader name: Gemplus GemPC Twin 00 00
response: 90 00 
response: 48 65 6C 6C 6F 20 77 6F 72 6C 64 21 90 00

Windows

D:\Documents and Settings\lroussea\My Documents\Visual Studio 2008\Projects\
sample\Debug> sample.exe
reader name: Gemplus USB Smart Card Reader 0
response: 90 00
response: 48 65 6C 6C 6F 20 77 6F 72 6C 64 21 90 00

Lessons learned

Reader name

The reader names are different on different plate forms. GNU/Linux and Mac OS X use the same PC/SC driver and the same (mostly) pcsc-lite so the name of an identical reader is the same. But Windows uses a different driver with a different PC/SC reader name.

Do not use the PC/SC reader name to identify a specific reader if you want to be portable.

SCardTransmit

pioRecvPci argument may be NULL (as in the sample) but shall not be left uninitialised on Windows. Otherwise SCardTransmit returns an error 0xE. This error code does not look like a WinSCard error code and is not documented in the SCardTransmit() API AFAIK.

UNICODE

If UNICODE is defined under Windows then the reader name is in UTF-16 and not easy to manipulate in a portable way. But I am not a Windows expert.

Unix uses UTF-8 so the name is the same in ASCII and UTF-8.

SCARD_AUTOALLOCATE

Mac OS X does not (yet) support memory (auto) allocation in the PC/SC layer. So you have to use a double call mechanism. One first call to get the size to allocate and one second call with a buffer allocated at the correct size.

In the example the memory auto allocation is used to get the list of connected readers. We see here that the C language is very low level regarding memory allocation.

Memory allocated by PC/SC has to be free-ed by PC/SC using SCardFreeMemory()

PCSC framework

Mac OS X uses Frameworks instead of classical Unix libraries. The difference (for our example) is the header files are not stored in /usr/include/ but in /System/Library/Frameworks/PCSC.framework/Headers/. You do not specify the header path to the compiler using -Ipath but use -framework PCSC instead. The compiler (gcc) takes care to find and use the correct framework.

pcsc_stringify_error()

pcsc_stringify_error() is a function specific to pcsc-lite. So we have to either not use it at all in a portable source code or re-implement a version on Windows. I decided to rewrite a limited version on Windows.

wintypes.h

Mac OS X does not use the exact same API definition than Windows or GNU/Linux for the WinSCard functions. Only standards types are used instead of Windows ones. For example:

int32_t SCardConnect(SCARDCONTEXT hContext,
        const char *szReader,
        uint32_t dwShareMode,
        uint32_t dwPreferredProtocols,
        LPSCARDHANDLE phCard,
        uint32_t *pdwActiveProtocol);

instead of

LONG SCardConnect(SCARDCONTEXT hContext,
 LPCSTR szReader,
 DWORD dwShareMode,
 DWORD dwPreferredProtocols,
 LPSCARDHANDLE phCard,
 LPDWORD pdwActiveProtocol);

The advantage is that the types used on Mac OS X are clearly defined by inttypes.h.

For example ULONG is defined as

typedef unsigned long ULONG;

but the long type from the C-language is different on Windows and GNU/Linux.

GNU/Linux (and Mac OS X) are LLP64 systems but Windows is a LP64 system. See 64-bit. So on GNU/Linux a long is 64-bits on a 64-bits CPU and on Windows a long is 32-bits on a 64-bits CPU.

To avoid problems Apple decided to explicitly fix the size using uint32_t.