JC101-2C: A simple counter (for smart card developers)

UPDATED (05/05/07): Fixed some bugs in the code.
UPDATED (22/12/11): Fixed more bugs in the code.
The Hello World program is a first program, but it doesn’t do anything interesting. The next step is to have a program that actually does something simple. We will here manage a simple counter with the following operations:

  • Get the balance of the counter.
  • Credit the counter (increase its value), and return the new value.
  • Debit the counter (decrease its value), and return the new value.


In addition, there will be a few constraints to be followed:

  • The counter can never become negative.
  • The counter has a limit.

In case of problem, we will simply use standard ISO status words: 6A80 for bad operations, and 6984 for invalid data. We will assign simple instruction codes to our three commands, for instance 02, 04, and 06.

The beginning of the class remains the same:

 1 package com.vetilles.counter1 ;
 2 
 3 import javacard.framework.* ;
 4 
 5 public class Counter1 extends Applet
 6 {

A convention is to define the constants at the beginning of the file. Here, we only need oour instruction codes (the status words are standard, and defined in a system interface).

 7   public static final byte INS_GET_BALANCE = 0x02 ;
 8   public static final byte INS_CREDIT = 0x04 ;
 9   public static final byte INS_DEBIT = 0x06 ;
10 
11   public static final short MAX_BALANCE = 10000 ;
12   public static final short MAX_CREDIT  =  5000 ;
13   public static final short MAX_DEBIT   =  1000 ;

The main difference with our first program is that there is here a need to manage some data, even though this data only represents a single value (the counter value). We of course want this value to be persistent; it must survive between sessions. This is easy to declare, since object fields are by default persistent in Java Card. We therefore simply need to declare a field (private to the class, since it is only visible within the class):

15   private short balance ;

This is all for data, and we can now get to the code, starting with the installation method, which simply invokes a constructor that initializes all the data:

17   private Counter1()
18   {
19     balance = 0 ;
20   }
21 
22   public static void install(byte[] buffer, short offset, short length)
23   {
24     (new Counter1()).register() ;
25   } 

The initialization of the balance field is not required here, as it is initialized to its default value, but making it explicit enhances the readability of the code: a reviewer will know that the intention of the developer is to initialize the value to 0.

The next method is the main process method, which includes the main processing code. Since our code is becoming more complex, it will only include the dispatching code, and the requests will be processed in separate methods.

27   public void process(APDU apdu)
28   {
29     byte[] buffer = apdu.getBuffer() ;
30     if (selectingApplet()) return ;
31 
32     switch(buffer[ISO7816.OFFSET_INS])
33     {
34       case INS_GET_BALANCE:
35         getBalance(apdu,buffer) ;
36         return ;
37       case INS_CREDIT:
38         credit(apdu,buffer) ;
39         return ;
40       case INS_DEBIT:
41         debit(apdu,buffer) ;
42         return ;
43       default:
44         ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED) ;
45     }
46   }

The selectingApplet method returns true when the method processes the SELECT command that actually selects the application. When the card receives a SELECT command, it looks for an application to select, and thensuccessively invokes the select and process methods. The select method performs all required initializations (here, it does nothing, which is the default behavior), and the process method returns any output required such as a FCI. By invoking the selectingApplet method, we can avoid an error being thrown in answer to the SELECT command.

The next step is to code the actual behavior of the commands. Getting the balance is the simplest operation, since it is unconditional and it does not require any incoming data:

48   private void getBalance(APDU apdu, byte[] buffer)
49   {
50     Util.setShort(buffer, (byte)0, balance) ;
51     apdu.setOutgoingAndSend((byte)0, (byte)2) ;
52   }

Outgoing data must be copied into an array before to be returned. Here, we first copy it into the APDU buffer, at offset 0; the second line sends the two first bytes of the APDU buffer.

Next, let's take a look at the debit method. Of course, more precautions are necessary here, because there are some conditions to be checked before to allow a debit operation to be allowed: the balance must be sufficient, and the debit amount must be not too large. In addition, it is required to receive some incoming data:

54   private void debit(APDU apdu, byte[] buffer)
55   {
56     short debit ;
57     if (apdu.setIncomingAndReceive() != 2)
58       ISOException.throwIt(ISO7816.SW_WRONG_LENGTH) ;
59     debit = Util.getShort(buffer,ISO7816.OFFSET_CDATA) ;
60 
61     if ((debit>balance) ||
62         (debit>MAX_DEBIT))
63       ISOException.throwIt(ISO7816.SW_WRONG_DATA) ;
64 
65     balance -= debit ;
66 
67     getBalance(apdu, buffer) ;
68   }

Is this version of the code correct? Of course not; otherwise, I would not ask the question. It actually contains a mistake that is very common from developers who come from C to Java.

The mistake is simple: it is here possible to debit a negative number. If the two data bytes received with the command are for instance "FF FF", then the amount debited will be "-1", which looks a lot like a credit. This comes from the fact that all integral values in Java are signed, so "FF FF" is not 65535, but -1. The corrected version of the method is shown below (we also have forbidden a debit of 0, because it does not make much sense):

54   private void debit(APDU apdu, byte[] buffer)
55   {
56     short debit ;
57     if (apdu.setIncomingAndReceive() != 2)
58       ISOException.throwIt(ISO7816.SW_WRONG_LENGTH) ;
59     debit = Util.getShort(buffer,ISO7816.OFFSET_CDATA) ;
60 
61     if ((debit>balance) || (debit<=0) ||
62         (debit>MAX_DEBIT))
63       ISOException.throwIt(ISO7816.SW_WRONG_DATA) ;
64 
65     balance -= debit ;
66 
67     getBalance(apdu, buffer) ;
68   }

The last method in the class is the credit method, which looks a lot like the debit method, with the exception of the arithmetic operation on the balance.

70   private void credit(APDU apdu, byte[] buffer)
71   {
72     short credit ;
73     if (apdu.setIncomingAndReceive() != 2)
74       ISOException.throwIt(ISO7816.SW_WRONG_LENGTH) ;
75     credit = Util.getShort(buffer,ISO7816.OFFSET_CDATA) ;
76 
77     if (((short)(credit+balance)>MAX_BALANCE) ||
78         (credit<=0) ||
79         (credit>MAX_CREDIT))
80       ISOException.throwIt(ISO7816.SW_WRONG_DATA) ;
81 
82     balance += credit ;
83 
84     getBalance(apdu, buffer) ;
85   }
86 }

The only interesting thing here is the cast operation in the first test. When we add two short values in Java, the result is an int. Since this type is not supported on all Java Card platforms, it is necessary to cast the result into a short before to use the data again. This cast is very important, because it is the operation that guarantees that a 16-bit virtual machine behaves exactly like a 32-bit virtual machine. Otherwise, overflow cases would not be properly considered.

One last note: in principle, it would also be necessary to verify that credit+balance is not negative (i.e., that it does not overflow). However, in the present application, the values of the constants are such that such an overflow is not possible.

That closes this first example. Unlike the Hello World example, it does something. However, it does not do much interesting. Next time, we'll move to a more interesting application.

Comments are disabled for this post