Advertisement: Support JavaWorld, click here!
January 1999
HOME FEATURES COLUMNS NEWS & REVIEWS TIPS Q&A JW RESOURCES ABOUT JW

Java Developer

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

Summary
In October, the 1.1 release of the OpenCard Framework was announced. There were a number of changes to the previous release, version 1.0, in the area of card terminals. These changes greatly simplify programming of card terminals and provide significant performance enhancements. OpenCard Framework 1.1 differs from 1.0 in the card terminal area in that exceptions are more concise; the CardID, which served a dual purpose in 1.0, now serves the single purpose of identifying the card using the ATR; event handling has been simplified; and the APDU classes have been made reusable to improve performance. Also, many of the convenience methods of CommandAPDU have been removed in order to contribute to slimlining the framework.

In order to provide developers with a good library for developing card terminals, this month's Java Developer contributors offer two card terminal implementations: one that uses RS232 signals to control the reader and one that uses a packet protocol to send data back and forth to the reader, which decodes it and forwards it on to the card. (4,500 words)

By Mike Wendler and Stephan Breideneich, with Rinaldo Di Giorgio


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:

Smart card-related functionality:

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:

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:

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:

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:

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.

There are two possibilities here: use the CardTerminalRegistry's polling thread or provide your own. The tradeoff is between ease of implementation and the level of control you have over the polling behavior. Using the CardTerminalRegistrys polling thread only requires you to let your terminal class implement the Pollable interface. The lone method poll allows you to check the terminal device's status on the given slot and compare it with the status of the flag in the class. Whenever you detect a status change on the slot, you toggle the flags value and invoke either the method cardInserted or the method cardRemoved, depending on the change you detected.

Here's an example implementation of a poll method (assuming the terminal has just one slot):

  

public void poll() throws CardTerminalException {
    boolean newStatus = isCardPresent (0);       // do we have a card in slot '0'?
    // _isCardInserted is a data field for recording the status
    if (_isCardInserted != newStatus) {          // if status changed
      _isCardInserted = ! _isCardInserted;       // the boolean flag holding the status
      if (_isCardInserted)                       // make sure the according events
        cardInserted (0);                        // are delivered to the listeners
      else {
        cardRemoved (0);
        // invalidateCachedCardID ();            // if you cache the ATR, clear it somewhere
      }
    }
  }

Note: Make sure the poll method completes quickly. Otherwise, you may cause bad overall performance of the system. This is because the CardTerminalRegistry object employs just one polling thread, which sequentially calls the poll method of all terminals that implement the Pollable interface.

By the way, the interval in which the card terminal registry polls the registered Pollable card terminals can be changed by calling its setPollInterval method. (The card terminal registry is a Singleton, which can be obtained by calling CardTerminalRegistry.getRegistry ().)

Providing your own polling mechanism may sometimes be necessary, in order to have more control over the timing. This requires you to implement your own polling thread. We won't explain that in detail here, rather, we'll leave it to you as an exercise.

Step 6. Return information about the smart card (ATR)
Responsible for this task are the two getCardID methods. Implementing the version with just one int argument really is a piece of cake, so we're starting with this one -- we assume your boss wants to see results quickly! Simply call the other getCardID method that requires two arguments. Pass through the value for the slot's ID as the first argument. Choose a suitable timeout value as the second argument. And that's it.

The code snippet below exemplifies this text.

  

public CardID getCardID (int slotID) throws CardTerminalException {
    return getCardID (slotID, TERMINAL_TIMEOUT);
  }

This is obviously the default version of the getCardID method. Its semantic is: return the ATR of the card but wait only a predefined period of time. So the only real work to be done here is to decide just how long this default period is to be and assign this value to a constant (named TERMINAL_TIMEOUT in our sample above) in your terminal class.

Now let's turn to the overloaded getCardID method, which does the real work. To obtain the ATR of a card, it must be powered up explicitly. But first you should be sure a card is actually inserted in the given slot (and throw a CardTerminalException if a card isn't in the slot). Then you retrieve the byte sequence that makes up the ATR. This will be device-specific, so we encapsulated this operation in a separate private method powerUpCard. The ATR returned by this method can then be used to construct an object of type CardID, which is OCF's wrapper class for an ATR.

If the ATR cannot be obtained within the given time period, a CardTerminalException should be thrown.

Here's an example of a getCardID method (see the note on implementation below for an explanation of the caching issue):

  

public CardID getCardID(int slotID, int ms) throws CardTerminalException {
    CardID cardID = null;
    byte[] cardStatus;
    // check whether card is inserted
    if ((getCardStatus(slotID) & STATE_CARD_INSERTED) == STATE_CARD_INSERTED) {
      // check whether card is powered (==> cachedATR != null)
      if (cachedATR == null)
        cardID = new CardID (getSlot(slotID), powerUpCard (slotID) );
      else
        cardID = new CardID (getSlot(slotID), cachedATR);
    }
    else
      cachedATR = null;
    return cardID;
  } // getCardID

Note: Powering up a smart card does take some time -- as does powering up any computer. Though smart cards seem to power up instantaneously compared to a Unix or Windows box, nonetheless you should strive to minimize these requests. With this in mind, we recommend you cache the ATR when retrieving it the first time (maybe in cardInserted). Within getCardID you then need to return the cached cardID object only (if it is set). Of course, you must make sure to clear the cache when the card is removed (for example, in cardRemoved).

Step 7. Communication with a smart card
The actual communication with a smart card is realized by sending APDUs to it via the sendAPDU method of the CardTerminal class. The default implementation must be changed in concrete subclasses by implementing the internalSendAPDU method.

As indicated above, there is a difference in the way APDUs are handled by the T=0 or T=1 protocol, respectively. In the first case, you need to carry out two commands with the card to send an APDU and receive a response APDU. In the latter case, one command is sufficient.

Here's an example:



protected ResponseAPDU internalSendAPDU (int slotID, CommandAPDU capdu, int timeout)
   throws CardTerminalException {
   // precondition: terminal command consists of one byte
   CommandAPDU sendAPDU = new CommandAPDU(capdu.getBytes().length + 1);
   // terminal command used to exchange data with the smartcard
   sendAPDU.append(0x15);                  
   // append the apdu to exchange with the smart card
   sendAPDU.append(capdu.getBytes());
   return new ResponseAPDU(ProtocolLayer.transmit(slotID, timeout, sendAPDU));

}

This code snippet is quite lengthy but the functionality boils down to sending a byte array (which comes wrapped into a CommandAPDU object) to the card and receiving a byte array from the card (which must be wrapped in a ResponseAPDU object before it's returned).

The example above uses a pure-Java implementation. If you want or need to use existing libraries to communicate with the terminal, you must use Java's mechanism to integrate native code, preferably JNI. We won't go deeper into this, but it basically means you need to create a wrapper class in Java, which you then use in the terminal class to access the physical device. See the Resources below for a good start link to JNI.

Step 8. Reset a card
The actual method being called by the framework to reset a card is reset. Again, this one isn't accessible, but there is an internalReset method for you where you must repower the card and return a fresh CardID object. Make sure to clear a cached card ID if you employ the caching solution proposed above.

Card terminal source code for various readers
The jar files, linked in the Resources section below, contain source code for card terminal implementations of the SmartMouse and the Litronic 210, and code for a generic packet protocol approach to card terminals called TLP224. In addition to the card terminal source code, we've also included some non-OCF Java code for developing a device driver to speak to a SmartMouse reader from GIS systems. The original code for the Litronics driver came from Solmer Hilger. It was modified slightly to work with the Java Card loader to be described in next month's column on loading Java Cards. Other changes were made to it to get it to function as a driver for the SmartMouse as well. The SmartMouse code that can be used without OpenCard comes to us from Jon Barber and was based upon original code by Sean Kelly at Smart Card Solutions. Note that a SmartMouse open card driver (similar to the Litronic driver) will be available by early January; check back soon.

Complex and simple readers
Readers are grouped into two categories: complex and simple. These terms aren't to be taken as commentary on the implementation of the readers, but as a quick way to understand what each reader can do for you and what you will be required to do in order to support it. What are the differences between complex and simple readers?

A simple reader doesn't perform any interpretation of the data being sent back and forth to the card; effectively, it is a transparent pass-through device. Smart cards implementing the ISO7816 standard can communicate with a host via a reader using the ISO7816 transmission protocol. The code to process readers of this type has been included in the drivers in the Resources below. This protocol has been implemented in the source code provided.

Simple readers use RS232 signaling to communicate with smart cards and have the following generic interface that varies slightly in the area of getting the ATR, reset, and card presence.

Complex readers have additional logic to perform processing on behalf of the requestor. So, instead of communicating with the card directly, complex readers interpret and act upong the bytes being sent to the card and perform additional operations in some cases.

Two simple readers
The two simple readers described in this section are the Litronic 210 and SmartMouse. Each of these readers uses electrical signaling for card detection and subsequent transmission of the ATR. Sending and receiving APDUs to the reader is accomplished by simply reading and writing data to the serial port and following the protocol.

Complex readers, which can also be expensive simple readers, perform processing directly on the reader with the small embedded microcontroller located in the reader. Yes, there is yet another computer in the card terminal in complex readers. In order to speak to this controller, the card terminal for the complex reader sometimes requires less code in the card terminal implementation than the simple reader, since the microcontroller performs many of the tasks normally carried out by the software on the host computer.

Some of the card terminal manufacturers tried to standardize in this area and came up with a protocol called TLP224. This protocol implements standard telecommunications practice; however, things like card insertion and removal detection weren't included in the specification.

Interfacing to a reader of this type can be very quickly done with OpenCard since you need only code a few methods. The most difficult part is coding the ISO7816 transmission protocol. This is fairly well documented in the source code.

Two complex readers
The GCR400 and others from GemPlus and the PE132 from DeLaRue Card Systems are examples of complex readers, in that these readers support the TLP224 packet protocol and other proprietary protocols with features that are sometimes documented and sometimes undocumented.

With these types of readers simple things like card insertion and removal can be difficult, since often you must send a communications packet to the device to determine reader presence as well as card presence. It can be done, but it makes for more complex code in comparison to RS232 signaling. We have provided an example of a TLP224 driver for the two readers mentioned.

The GCR400 reader provides card inserted indication via the Ring Indicator(RI) RS232 signal line. Ring Indicator isn't commonly supported on RS232, so what do you do? Make a special cable for it, where you would, say, take the RI pin and connect it to the CD pin so that your OpenCard software could read the CD pin. In other words, we route the RI signal to a signal we can read. If we did this for the GCR400, then we might have to do something different for the PE132, since it doesn't use RI to tell you a card has been inserted. The point here is that even with some standard like TLP224, there is much room for interpretation. One of the strengths of OpenCard is that it allows you to support each of these readers with specific implementations -- so feel free to take the source code and make it specific to a particular reader.

Conclusion
In this article we've explained the interfaces and their relationship to OpenCard as well as the questions a developer should ask in order to write a card terminal. We've followed our explanations with several implementations of card terminals for simple and complex readers. As we explained, relatively few methods are required for the implementation of a card terminal and writing OpenCard card terminals isn't hard. And, thanks to input from your peers in the developer community, we hope we've empowered you to create a card terminal library for OpenCard that makes using smart cards with different readers an easy project.

We would like to acknowledge special thanks to the Moshe Levy of Sun Microsystems for providing information on the PE132 reader and to Soren Hilmer for doing the hard work on the Litronic driver as well as Mike Montomgery of Schlumberger for optimizing the Litronic Card Terminal and to William King of Sun Microsystems for describing the TLP224 Error messages in great detail. You can see that OpenCard has a lot of individual contributors.

All of the source code provided requires further development and/or testing before being used in mission-critical systems. The purpose of providing code of this type is to offer starting points for you, the developer, to develop OpenCard card terminal implementations; it is our hope that this effort will relieve the widespread concern with the unavailability of different types of readers.


About the author
Stephan studied Business Informatics in Mannheim and has been a freelance software developer since he was 16. His current interests include distributed systems, system programming, and databases, all in connection with Java.

Mike holds a diploma in Business Informatics from the Martin-Luther-University in Halle and a M.Sc. in Information Technology from Leicester University. He is an aficionado of object-oriented analysis and design with patterns, components, and frameworks and switched from C++ to Java as his favorite programming language with Version 1.0.

Both Stephan and Mike are responsible for the card terminal layer in the OCF team at the IBM Development/Research Lab in Boeblingen right now. Besides that they have joined forces recently to offer consulting and software development services with their startup company "interface solutions."


Advertisement: Support JavaWorld, click here!


HOME |  FEATURES |  COLUMNS |  NEWS & REVIEWS |  TIPS |  Q&A |  JW RESOURCES |  ABOUT JW |  FEEDBACK

Copyright © 2001 JavaWorld.com, an IDG Communications company