Resources release in PySCard

With PySCard version 2.2.2 (New version of PySCard: 2.2.2) it is now possible to release/free the PC/SC context used by some objects.

Sample

As see in PCSC sample in Python a typical PySCard code looks like:

#! /usr/bin/env python

from smartcard.System import readers

# define the APDUs used in this script
SELECT = [0x00, 0xA4, 0x04, 0x00, 0x0A, 0xA0, 0x00, 0x00, 0x00, 0x62,
    0x03, 0x01, 0x0C, 0x06, 0x01]
COMMAND = [0x00, 0x00, 0x00, 0x00]

# get all the available readers
r = readers()
print "Available readers:", r

reader = r[0]
print "Using:", reader

connection = reader.createConnection()
connection.connect()

data, sw1, sw2 = connection.transmit(SELECT)
print data
print "Select Applet: %02X %02X" % (sw1, sw2)

data, sw1, sw2 = connection.transmit(COMMAND)
print data
print "Command: %02X %02X" % (sw1, sw2)

You can see that a connection is created with connection = reader.createConnection() and then the card is connected with connection.connect(), but the card is never explicitly disconnected and the connection is never released.

It was already possible to use:

connection.disconnect()

It is now also possible to use:

connection.release()

The code becomes (after convertion to Python 3):

#! /usr/bin/env python

from smartcard.System import readers
from smartcard.util import toASCIIString

# define the APDUs used in this script
SELECT = [0x00, 0xA4, 0x04, 0x00, 0x0A, 0xA0, 0x00, 0x00, 0x00, 0x62,
    0x03, 0x01, 0x0C, 0x06, 0x01]
COMMAND = [0x00, 0x00, 0x00, 0x00]

# get all the available readers
r = readers()
print("Available readers:", r)

reader = r[0]
print("Using:", reader)

connection = reader.createConnection()
connection.connect()

data, sw1, sw2 = connection.transmit(SELECT)
print(data)
print(f"Select Applet: {sw1:02X} {sw2:02X}")

data, sw1, sw2 = connection.transmit(COMMAND)
print(data)
print(toASCIIString(data))
print(f"Command: {sw1:02X} {sw2:02X}")

connection.disconnect()
connection.release()

Output

Available readers: ['Gemalto PC Twin Reader']
Using: Gemalto PC Twin Reader
[]
Select Applet: 90 00
[72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33]
Hello world!
Command: 90 00

If you do not want to explicitly call .disconnect() and .release() you can use a context manager and the Python with statement.

[...]

with reader.createConnection() as connection:
    connection.connect()

    data, sw1, sw2 = connection.transmit(SELECT)
    print(data)
    print(f"Select Applet: {sw1:02X} {sw2:02X}")

    data, sw1, sw2 = connection.transmit(COMMAND)
    print(data)
    print(toASCIIString(data))
    print(f"Command: {sw1:02X} {sw2:02X}")

Or you can also use the del instruction to call the object finalizer.

del connection

CardRequest

You can also use with with a CardRequest object.

#! /usr/bin/env python

from smartcard.CardRequest import CardRequest

print("Insert a card within 10 seconds")
with CardRequest(timeout=10) as req:
    svc = req.waitforcard()
    svc.connection.connect()
    r = svc.connection.transmit([0,0,0,0])
    print(r)
    svc.connection.disconnect()
    svc.connection.release()

Ouput

Insert a card within 10 seconds
([], 109, 0)

CardService

And also with a CardService object.

#! /usr/bin/env python

from smartcard.CardRequest import CardRequest

print("Insert a card within 10 seconds")
with CardRequest(timeout=10) as req:
    with req.waitforcard() as svc:
        svc.connection.connect()
        r = svc.connection.transmit([0,0,0,0])
        print(r)

Traces

It is possible and easy to trace the actions performed by PySCard using an smartcard.Observer object.

#! /usr/bin/env python

from time import sleep
from smartcard.CardRequest import CardRequest
from smartcard.CardConnectionObserver import ConsoleCardConnectionObserver

print("Insert a card within 10 seconds")
with CardRequest(timeout=10) as req:
    with req.waitforcard() as svc:
        observer = ConsoleCardConnectionObserver()
        svc.connection.addObserver(observer)

        svc.connection.connect()
        r = svc.connection.transmit([0,0,0,0])
        print(r)

sleep(10)
print("end")

Ouput

Insert a card within 10 seconds
connecting to Gemalto PC Twin Reader
> 00 00 00 00
<  [] 6D 00
([], 109, 0)
disconnecting from Gemalto PC Twin Reader
release from Gemalto PC Twin Reader
end

You can see that .disconnect() and .release() methods are called and traced.

The traces are displayed 10 seconds before the "end", i.e. when the two with exit, not when the program ends (and the garbage collector is used to clean each object).

Why

The CardConnection().release() method has been added to release the PC/SC context and allow pcscd (the daemon used in pcsc-lite) to exit automatically before the end of the Python program. See the issue PCSCCardConnection.__del__ method raises exceptions that cannot be easily handled in python code. #223 and in particular this comment https://github.com/LudovicRousseau/pyscard/issues/223#issuecomment-2773120282:

pcscd should not auto exit before all the contexts have been released.

Btw this was indeed the other (only loosely related) issue that I mentioned at the end of initial post.

I.e. that using pyscard in a main process or a thread always holds up pcsc-lite running (unless I missed something, iirc with connection management in C code), and there seem to be no way to say "please close the damn context, time to read cards was gone 10 hours ago, and this silly NFC reader eats up 300 milliamps" :)

So would be nice to have a way to cleanly close such context/connection manually (allowing pcsc-lite to exit too, and without purging pyscard from memory with subprocess) for other reasons as well, but again, probably a separate issue.

And I think context != connection, as latter are managed somewhere in C, not in python, but might be wrong or misremembering.

The problem is that the user has a smart card reader that consumes 300 milliamps even when it's doing "nothing".

So it's a good idea to let pcscd exit so that the USB device will be put in low power thanks to the autosuspend Linux feature.

I haven't checked the behavior on macOS or Windows.

Conclusion

Not all users will need or use the .release() method. If your Python program runs for a long time and you only use the smart card at certain times, it may be a good idea to take a look at your code and update it.