How to write a CardTerminal class for simple and complex readers in an OpenCard environment

Learn the ropes of writing the software part for your card terminal device using the OpenCard Framework

So, you've been reading previous Java Developer columns on smart cards and the OpenCard Framework (OCF). You have the OpenCard Framework software and your card terminal device. Now, what exactly do you need to do to make the two work together? In this edition of Java Developer, we'll explain, step-by-step, exactly how to get your software and hardware to cooperate. We hope this article will convince you that this is a fairly easy process!

The OpenCard Framework offers general functionality for dealing with card terminals. (Although they're often called card readers, the OCF uses the more general, all-encompassing term card terminal for these devices.) Unfortunately, no concrete card terminal class was provided in the core and opt packages (although there are a few example implementations in the OCF Reference Implementation). Including a concrete card terminal class simply isn't possible due to the sheer variety of existing devices. This means some work remains to be done by the "terminal integrator" -- yes, that's you!

The missing terminal-dependent functionality is comprised of the following facilities:

  • Creation of the card terminal object
  • Initialization/shutdown of the card terminal object
  • Provision of information about the card terminal type

Smart card-related functionality:

  • Detection of insertion or removal of a smart card
  • Powering/unpowering of the card
  • Return of information about the smart card (ATR)
  • Communication with a smart card
  • Resetting of a card

In the following section, we'll show you what to implement in order to realize this functionality.

Writing an OCF card terminal step by step

Step 1. Obtain the required information & make the basic decisions

To ensure an efficient development process, it is recommended that you collect the following information and make a few decisions right from the beginning:

A. Create a new factory class or reuse an existing one

Objects of subclasses of CardTerminal are brought into being by means of a factory object. If you already have such a factory class, reuse it and change it such that it can handle the new CardTerminal class too. If you don't have a factory class yet, well, write one from scratch! (We'll explain how to do this later on in the article.)

B. Determine the name of the terminal family

The card terminal device should come with a name. So take this name and make a name string from it that complies with the Java syntax.

C. Determine the number of slots

The number of slots will default to one in most cases, but as the smart card market expands we'll see devices with more than one slot.

D. Enable communication over javax.comm or native methods

Your Java terminal class must somehow communicate with the physical device. This can be accomplished in one of several ways. If the terminal comes with prebuilt libraries, you can use those by means of Java's native method interface (JNI). The advantage to this is that a lot of the required functionality is already implemented. The downside is, of course, that this approach renders your card terminal implementation platform-dependent. The second possibility is to use Java's mechanism of accessing the serial port of your machine using the package javax.comm. This package isn't included in the current JDK release but it is part of Java's extension classes in the just-released major upgrade to JDK 1.2 (now known as the Java 2 platform). A beta release can be obtained separately from the Java Developer Connection on Sun's java.sun.com site. Currently javax.comm supports only serial and parallel devices.

E. Provide your own polling or use the polling of the card terminal registry

Polling of the slot(s) (to be informed when a smart card is being inserted or removed) can be done by the card terminal registry (which deals with all registered terminals in the framework) or by the card terminal object itself. The first approach is the easiest and therefore is the recommended way. In some cases, however, you may want to exercise more control over the polling process, in which case the terminal object should take care of the polling. Future devices may even support interrupts, which, of course, would be preferable to polling.

F. Determine which protocols the supported smart cards use

Most smart cards either use the "T=0" or the "T=1" protocol. The relevant difference for OCF lies in the number of commands required to send an APDU (Application Protocol Data Unit) to the card and get a response APDU (technically we're talking about APDUs with both additional data fields, also called case 4 commands by the ISO 7816-4 standard).

View this enumeration as an introduction only. We'll go into greater detail for each of the above mentioned issues when we explain the actual implementation.

Step 2. Creation of the terminal object

Objects of the card terminal class you're going to implement aren't created directly (via new) but only indirectly by a factory class. That means that writing a card terminal usually means not only implementing the CardTerminal class but a factory class as well. Figure 1 shows you a UML class diagram of the classes and interfaces involved.

Figure 1: A UML class diagram of the classes and interfaces involved

Okay. As the factory class depends on the terminal class, let's start with this one. Create a subclass of the abstract CardTerminal class and name it something like <yourTerminalsName>CardTerminal (OCF convention).

Here's a list of the inherited abstract methods you'll have to implement:

  • public abstract CardID getCardID(int slotID) throws CardTerminalException

  • public abstract CardID getCardID(int slotID, int ms) throws CardTerminalException

  • public abstract boolean isCardPresent(int slotID) throws CardTerminalException

  • public abstract void open() throws CardTerminalException

  • public abstract void close() throws CardTerminalException

  • protected abstract Properties internalFeatures(Properties features)

  • protected abstract CardID internalReset(int slot, int ms)

  • protected abstract ResponseAPDU internalSendAPDU(int slot, CommandAPDU capdu, int ms)

If you're like us, you'll want to see some code running soon. So simply write empty bodies for the methods mentioned above, returning null from the getCardID, internalFeatures, internalReset, and internalSendAPDU methods, and false from isCardPresent. The constructor is easy. Invoke the super class's constructor, passing through all arguments, and then call the addSlots method with the number of slots as an argument.

Here's an example:

protected MyCardTerminal (String name, String type, String address) throws CardTerminalException { super (name, type, address); addSlots(1); }

This is sufficient at this stage to compile this class so we can use it in the factory. We'll talk about the actual implementation of the other methods as soon as we need them. For your terminal factory class you must implement the CardTerminalFactory interface. Give that a name something like <yourTerminalFamiliesName>CardTerminalFactory (OCF convention).

The interface CardTerminalFactory provides the following methods:

  • public void createCardTerminals(CardTerminalRegistry ctr, String[] terminalInfo) throws CardTerminalException, TerminalInitException

  • public void open() throws CardTerminalException

  • public void close() throws CardTerminalException

You can safely ignore the open/close methods for now -- that is, write an empty method body. Okay, now things are starting to get interesting! createCardTerminals is responsible for parsing the configuration information passed and for filtering out the information it knows something about. The passed String array's first element contains the name of a terminal type. The Factory interface must determine whether this name belongs to a card terminal class it knows about. If it does know about it, the Factory interface instantiates the class and registers the resulting object with the CardTerminalRegistry.

Here's an example implementation:

public void createCardTerminals(CardTerminalRegistry ctr, String [] terminalInfo) throws CardTerminalException, TerminalInitException { // is it my terminal? if (terminalInfo[1].equals ("MyTerminalTypeName")) { // if the terminal requires address information an additional parameter is needed if (terminalInfo.length != 3) throw new TerminalInitException("createCardTerminals: " + "Factory needs 3 parameters for My terminal"); // create the actual terminal object and register it with the CardTerminalRegistry ctr.add (new MyCardTerminal (terminalInfo [TERMINAL_NAME_ENTRY], terminalInfo [TERMINAL_TYPE_ENTRY], terminalInfo [TERMINAL_ADDRESS_ENTRY])); } else throw new TerminalInitException("Type unknown: " + terminalInfo[TERMINAL_NAME_ENTRY]); }

Now we're ready to see some code in action! For this we'll use one of the short demo programs provided with the OCF software, which will print out information about the terminals registered in the card terminal registry. You only need to change the opencard.properties file such that it includes information about your special new terminal. For a more thorough explanation about how OCF can be configured, have a look at the "README-configuration.html" which should be in the top directory of your OCF installation.

Basically, your opencard.properties file should contain a line like this:

OpenCard.terminals = YourCardTerminalFactory|MyTerminal1|MyTerminalTypeName|<yourPortNr>

The OpenCard.terminals entry may contain several terminal entries, which should comprise this information:

  • The fully-qualified name of your terminal factory class

  • A unique and Java syntax-compliant name for the terminal instance (because there may be several terminals of the same type attached to your computer)

  • The type name of your terminal (the one you used in the createCardTerminals method above)

  • The port/device information your terminal class can deal with (see the implementation note below)

Now, run the demo program

java demos.samples.GetTerminalInfos

and you will (hopefully) see some information being printed about the terminals that are now registered.

Note: Our reference to port/device info may sound pretty vague to you, and rightly so. But alas, this is platform- and terminal-specific, so we can hardly give you more specific hints here. Simply put, the terminal class must be able to use this information to connect to the physical device. For example, for the IBM 5948 test terminal, we only had to put a 1 or 2 here in order to indicate the port on which to connect to the physical device. With a javax.comm implementation, you may have to use something like Com1 on a WinTel Platform or SerialA on a Solaris box to denote the first serial port.

Step 3. Initialization/shutdown of the terminal object

Quite often, specific operations have to be carried out in order to start up the terminal device or shut it down when it is no longer in use. These operations belong in the open and close methods you've left empty until now. The framework invokes these methods during its start or shutdown process. The OCF has a requirement here only if your class implements the Pollable interface (see Step 5, "Detect the insertion or removal of a smart card" below). In this case, you must make sure your terminal object is added to the CardTerminalRegistry's list of terminals it has to poll. You do this by calling its addPollable method in open, and removePollable in close, respectively. Either open or close should throw a CardTerminalException if something goes wrong in the process of initializing or shutting down the terminal device.

Step 4. Provide some information about the terminal type

The most basic information about your terminal (which you provided in the opencard.properties file) is returned by the features method of the CardTerminal class in a Properties object. This method is final so there is no way to change it. But it calls the non-final method internalFeatures, which you can use to add specific properties of your terminal to the Properties object passed. (No need to create a new object!)

Step 5. Detect the insertion or removal of a smart card

Your terminal should somehow make it possible to query its status and find out whether a card is inserted in one of its slots. Implement the isCardPresent method and provide that functionality there. Just query the terminal status and return true if a card is currently inserted in the slot with the ID passed. If no card is present, return false.

The OCF contains a CardTerminalListener interface that must be implemented by client classes that want to receive CardTerminalEvents. By means of these events, the client is able to detect the insertion or removal of a card into or from a slot. You guessed it, it's your terminal's task to fire these events. (And it's your task to make it work.) Basically, you must detect and record the status changes within the class. So add a boolean flag to the class that is to record the card insertion status. Now, where you do the actual work depends on the polling mechanism you choose.

1 2 3 Page 1
Page 1 of 3