22 Apr 2011

Unusual usage of exceptions in JavaCard development (Updated)

If you are reading this blog most probably you already know that development of JavaCard applets has own specificities. Sometimes we need to save few bytes to fit applet to card memory and speed of applet is always important. In this post I’d like to show you unusual usage of exceptions to optimize speed and size of the applet.
Let’s imagine you need to read transparent file on the card but you don’t know its size in advance. Usual practice is analyzing response to select and extracting size information. In our test case we have to read PLMNSel file which contains Mobile Network Code and Mobile Country Code. Each PLMN information length is 3 bytes. Afterwards we can do something with this information but I skip it to show you the main idea. Let's have a look to the code:

package testappletwithbuffer;

import sim.access.*;
import javacard.framework.*;

public class TestAppletWithBuffer extends javacard.framework.Applet {
    
    private SIMView gsmFile;
    private byte[] plmn;
    private byte[] response;
    
    protected TestAppletWithBuffer() {
        gsmFile = SIMSystem.getTheSIMView ();
        plmn = new byte[3];
        response = new byte[15];
    }

    public static void install(byte[] bArray, short bOffset, byte bLength){
        TestAppletWithBuffer refApplet = new TestAppletWithBuffer();
        refApplet.register(bArray, (short) (bOffset + 1), (byte) bArray[bOffset]);
        refApplet.readPLMNSel();
    }

    public void process(APDU apdu) throws ISOException {
          // ignore the applet select command dispatched to the process
        if (selectingApplet()) {
            return;
        }
    }
    
    public void readPLMNSel() {
        gsmFile.select ((short) SIMView.FID_DF_GSM);
        gsmFile.select ((short) SIMView.FID_EF_PLMNSEL
                        , response
                        , (short) 0
                        , (short) response.length);
        short fileOffset = 0;
        short fileLength = Util.makeShort(response[2], response[3]);
        
        // reads the PLMN information to plmn buffer
        for (fileOffset = 0; fileOffset < fileLength; fileOffset += plmn.length) {
            gsmFile.readBinary (fileOffset
                                , plmn
                                , (short) 0
                                , (short) plmn.length);
        }

    }
}

Let me show you how we can optimize this applet:

package testappletwithexception;

import sim.access.*;
import javacard.framework.*;

public class TestAppletWithException extends javacard.framework.Applet {

    private SIMView gsmFile;
    private byte[] plmn;

    public TestAppletWithException() {
        gsmFile = SIMSystem.getTheSIMView();
        plmn = new byte[3];
    }

    public static void install(byte[] bArray, short bOffset, byte bLength) {
        TestAppletWithException refApplet = new TestAppletWithException();
        refApplet.register(bArray, (short) (bOffset + 1), (byte) bArray[bOffset]);
        refApplet.readPLMNSel();
    }

    public void process(APDU apdu) {
        // ignore the applet select command dispatched to the process
        if (selectingApplet()) {
            return;
        }
    }
    
    public void readPLMNSel() {
        short fileOffset = 0;
        gsmFile.select ((short) SIMView.FID_DF_GSM);
        gsmFile.select ((short) SIMView.FID_EF_PLMNSEL);
        
        try {
            // reads the PLMN information to plmn buffer
            for (fileOffset = 0; fileOffset < (short) 0xFFFF; fileOffset += plmn.length) {
                gsmFile.readBinary(fileOffset
                                   , plmn
                                   , (short) 0
                                   , (short) plmn.length);   
            }
        } catch (SIMViewException e) {
            // normal case
        }
    }
}

Such kind of usage of exception could be weird to regular Java developers. But compare generated bytecodes:

Applet with exception Applet with buffer
.method public readPLMNSel()V 8 {
    .stack 5;
    .locals 1;

    L0:  sconst_0;
         sstore_1;
         getfield_a_this 0;  // ref gsmFile
      sspush 32544;
         invokeinterface 2 10 7; // SIMView
         getfield_a_this 0;  // ref gsmFile
         sspush 28464;
         invokeinterface 2 10 7; // SIMView
    L1:  sconst_0;
         sstore_1;
         goto L3;
    L2:  getfield_a_this 0; // ref gsmFile
         sload_1;
         getfield_a_this 1; // ref plmn
         sconst_0;
         getfield_a_this 1; // ref plmn
         arraylength;
         invokeinterface 5 10 9; // SIMView
         pop;
         sload_1;
         getfield_a_this 1; // ref plmn
         arraylength;
         sadd;
         sstore_1;
    L3:  sload_1;
         sconst_m1;
         if_scmplt L2;
    L4:  goto L6;
    L5:  pop;
    L6:  return;
    .exceptionTable {
         // start_block end_block
         // handler_block catch_type_index
         L1 L4 L5 9;
    }
}
.method public readPLMNSel()V 8 {
    .stack 5;
    .locals 2;

    L0:  getfield_a_this 0;  // ref gsmFile
         sspush 32544;
         invokeinterface 2 10 7; // SIMView
         getfield_a_this 0;  // ref gsmFile
         sspush 28464;
         getfield_a_this 2;  // ref response
         sconst_0;
         getfield_a_this 2;  // ref response
         arraylength;
         invokeinterface 5 10 6; // SIMView
         pop;
         sconst_0;
         sstore_1;
         getfield_a_this 2;  // ref response
         sconst_2;
         baload;
         getfield_a_this 2;  // ref response
         sconst_3;
         baload;
         invokestatic 11;  // makeShort
         sstore_2;
         sconst_0;
         sstore_1;
         goto L2;
    L1:  getfield_a_this 0;  // ref gsmFile
         sload_1;
         getfield_a_this 1;  // ref plmn
         sconst_0;
         getfield_a_this 1;  // ref plmn
         arraylength;
         invokeinterface 5 10 9; // SIMView
         pop;
         sload_1;
         getfield_a_this 1;  // ref plmn
         arraylength;
         sadd;
         sstore_1;
    L2:  sload_1;
         sload_2;
         if_scmplt L1;
    L3:  return;
}

As you can see exception bytecode shorter and (here you have to trust me :)) it will work faster.

In given example bytecode size isn’t noticeably less but if you have more complex logic of verification it could make sense. If you compile to above two applets and compare size of IJC files it will be 339 and 363 bytes accordingly. So you will save 24 bytes and will win a little on speed.

Update:
After publishing post I figured out how to more optimize the version with exception. Actually we don't need to check anything at all and if we will replace for loop with while like below we will win even more on the size and speed:

while(true) {
    gsmFile.readBinary(fileOffset
                       , plmn
                       , (short) 0
                       , (short) plmn.length);
    fileOffset += plmn.length;
}

And bytecode in this case will be very short:

.method public readPLMNSel()V 8 {
    .stack 5;
    .locals 1;

    L0:  sconst_0;
         sstore_1;
         getfield_a_this 0;  // ref gsmFile
         sspush 32544;
         invokeinterface 2 10 7;  // sim/access/SIMView
         getfield_a_this 0;       // ref gsmFile
         sspush 28464;
         invokeinterface 2 10 7;  // sim/access/SIMView
    L1:  getfield_a_this 0;       // ref gsmFile
         sload_1;
         getfield_a_this 1;       // ref plmn
         sconst_0;
         getfield_a_this 1;       // ref plmn
         arraylength;
         invokeinterface 5 10 9;  // sim/access/SIMView
         pop;
         sload_1;
         getfield_a_this 1;       // ref plmn
         arraylength;
         sadd;
         sstore_1;
         goto L1;
    L2:  pop;
         return;
    .exceptionTable {
        // start_block end_block handler_block catch_type_index
        L1 L2 L2 9;
    }
}
So there is no limit for perfection :)
Update 2:
I've decided do not leave you with the statement "just trust me" concerning the speed. Let me explain why it is faster:
  • There is no implicit garbage collection on JavaCard. You have to call it explicitly hence no need to unwind stack during exception
  • Exceptions are not fully object as in terms of "big" Java. Usually it is just error code from native layer
As matter of fact usage of exceptions is cheap in JavaCard

1 comment:

Anonymous said...

Thanks! Looking forward to your new posts.