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

6 Apr 2011

How to develop SCWS Servlet?

I'd like to continue the subject of Smart Card Web Server (SCWS) and explain in this post how to develop servlets for SCWS. Servlet for SCWS from one point is regular JavaCard Applet but at the same it has some differences. Let me explain.

Servlet has the following properties:
  • Triggers upon HTTP request reception
  • Has to be developed using dedicated ETSI 102.588 API
  • Mapped on one or several URIs (e.g.“/operator/app”)
There are two kind of servlets:
  • Dynamic content generation
  • Interception (usage tracking)
Servlet supports the following HTTP Request methods:

GETRequests the specified resource
HEADRequests meta-information on a resource (no body)
PUTUploads (add or replace) a resource on the server
POSTSubmits data to be processed to the identified resource
DELETEDeletes the specified resource on the server
OPTIONSReturns the HTTP methods supported for the specified URI (debug purpose)
TRACEEchos back the received requests (debug purposes)

CONNECT method isn't supported by ETSI TS 102.588 standard.

SCWS API has defined in uicc.scws hence you have to import it:

import uicc.scws.*;

Class has to be declared as follows:

class ServletHelloWorld extends Applet
      implements AppletEvent, ScwsExtension
  • Class should extend Applet as it is JavaCard standard applet
  • It has to implement ScwsExtension interface to be able to trigger by SCWS
  • It has to implement AppletEvent interface to define uninstall() method
  • It could implement ToolkitInterface interface if the servlet should be triggered as STK applet
Methods which have to be implemented:
  • JavaCard mandatory methods: process(), install()
  • ScwsExtension mandatory methods: doDelete(), doGet(), doHead(), doOptions(), doPost(), doPut(), doTrace()
Ok, let's have a look to real source code:

package firstservletpackage;

/*
 * Imported packages
 */
import javacard.framework.*;
import uicc.scws.HttpRequest;
import uicc.scws.HttpResponse;
import uicc.scws.ScwsConstants;
import uicc.scws.ScwsException;
import uicc.scws.ScwsExtension;
import uicc.scws.ScwsExtensionRegistry;

public class FirstServlet extends javacard.framework.Applet
                          implements AppletEvent, ScwsExtension {
    /** the servlet url */
    public final static byte[] url = { '/', 'F', 'S' };
    public final static byte[] appId = { 'F', 'i', 'r', 's', 't', 'S', 'e'
                                        ,'r', 'v', 'l', 'e', 't' };
    public final static byte[] CRLF = { '\r', '\n' };
    public static final byte[] SPACE = { ' ' };
    /** error status word when install parameter are invalid */
    public final static short SW_INVALID_INSTALL_PARAMETER = (short) 0x6F06;
    /** install tag: should find the http server aid inside */
    public final static byte INSTALL_TAG = (byte) 0x71;

    public final static byte[] METHOD_GET = { 'G', 'E', 'T' };
    public final static byte[] TEXT_PLAIN = { 't', 'e', 'x', 't', '/', 'p'
                                             ,'l', 'a', 'i', 'n' };

    // Temporary operation buffer
    public byte[] temporaryBuffer;
    public final static short TEMPORARY_BUFFER_LENGTH = (short) 100;

    public final static byte[] REQUEST_BODY = { 'R', 'e', 'q', 'u', 'e', 's'
                                             ,'t', ' ', 'B', 'o', 'd', 'y', ':' };
    public final static byte[] PROTOCOL = { 'P', 'r', 'o', 't', 'o', 'c', 'o'
                                           ,'l', ':' };
    public final static byte[] CONTENT_TYPE = { 'C', 'o', 'n', 't', 'e', 'n'
                                             ,'t', ' ', 'T', 'y', 'p', 'e', ':' };
    public final static byte[] HEADER_UA = { 'H', 'e', 'a', 'd', 'e', 'r', ' '
                                            ,'U', 's', 'e', 'r', ' '
                                            , 'A', 'g', 'e', 'n', 't', ':' };
    public final static byte[] METHOD = { 'M', 'e', 't', 'h', 'o', 'd', ':' };
    public final static byte[] QUERY_STRING = { 'Q', 'u', 'e', 'r', 'y', ' '
                                             ,'S', 't', 'r', 'i', 'n', 'g', ':' };
    public final static byte[] REQUEST_URI = { 'R', 'e', 'q', 'u', 'e', 's'
                                              ,'t', ' ', 'U', 'R', 'I', ':' };
    public final static byte[] TRACE = { 'T', 'R', 'A', 'C', 'E' };
    public final static byte[] ACCEPT = { 'A', 'c', 'c', 'e', 'p', 't', ':' };
    public final static byte[] ACCEPT_LANG = { 'A', 'c', 'c', 'e', 'p', 't'
                              ,'-', 'L', 'a', 'n', 'g', 'u', 'a', 'g', 'e', ':' };
    public final static byte[] CONNECTION = { 'C', 'o', 'n', 'n', 'e', 'c'
                                             ,'t', 'i', 'o', 'n', ':' };
    public final static byte[] HOST = { 'H', 'o', 's', 't', ':' };
    public final static byte[] REFERER = { 'R', 'e', 'f', 'e', 'r', 'e', 'r', ':' };
    public final static byte[] USER_AGENT = { 'U', 's', 'e', 'r', '-', 'A'
                                             ,'g', 'e', 'n', 't', ':' };
    public final static byte[] ACCEPT_ENCODING = { 'A', 'c', 'c', 'e', 'p', 't'
                                                  , '-', 'E', 'n', 'c', 'o', 'd'
                                                  , 'i', 'n', 'g', ':' };
    public final static byte[] ALLOW = { 'A', 'l', 'l', 'o', 'w' };
    public final static byte[] ALLOW_OPTIONS = { 'G', 'E', 'T', ','
                                       , 'T', 'R', 'A', 'C', 'E'
                                       , ',', 'O', 'P', 'T', 'I', 'O', 'N', 'S' };
    public final static byte[] HTTP_10 = { 'H', 'T', 'T', 'P', ':'
                                          , '/', '1', '.', '0' };
    public final static byte[] HTTP_11 = { 'H', 'T', 'T', 'P', ':'
                                          , '/', '1', '.', '1' };

    public final static byte[] HELLO = { 'H', 'e', 'l', 'l', 'o' };

    /**
     * Constructor of servlet
     */
    public FirstServlet(byte[] buffer, short offset, byte length) {
        // First LV is instance AID
        short aid = offset;
        offset += buffer[offset] + (byte) 1;
        // Second LV is Privilege
        offset += buffer[offset] + (byte) 1;
        // Third LV is specific install parameter (extract from TAG C9)
        offset++; // skip C9 Length
        // Register the new applet instance to the JCRE
        register(buffer, (short) (aid + (short) 1), buffer[aid]);
        // Register application id,there is corresponding appId in the Run/Debug
        // configuration for URL Mapping
        // if you update this appId in current code,you may need update
        // corresponding value there for URL Mapping
        ScwsExtensionRegistry.register(this, appId, (short) 0,
                (short) appId.length);
        try {
            // Create a temporary buffer for read/write
            temporaryBuffer = JCSystem.makeTransientByteArray(
                    TEMPORARY_BUFFER_LENGTH, JCSystem.CLEAR_ON_RESET);
        } catch (SystemException se) {
            // create buffer in persistent memory as not enough transient is
            // available
            temporaryBuffer = new byte[TEMPORARY_BUFFER_LENGTH];
        }

    }

    /**
     * Method called by the JCRE at the installation of the applet
     * 
     * @param bArray
     *            the byte array containing the AID bytes
     * @param bOffset
     *            the start of AID bytes in bArray
     * @param bLength
     *            the length of the AID bytes in bArray
     */
    public static void install(byte[] bArray, short sOffset, byte bLength)
            throws ISOException {
        new FirstServlet(bArray, sOffset, bLength);
    }

    /**
     * Method called by the JCRE, uninstall the applet
     */
    public void uninstall() {
        ScwsExtensionRegistry.deregister(this);
    }

    /**
     * Method called by the JCRE, once selected
     * 
     * @param apdu
     *            the incoming APDU object
     */

    public void process(APDU apdu) throws ISOException {
        // ignore the applet select command dispached to the process
        if (selectingApplet()) {
            return;
        }
        // actually no IO methods needed for the server.
        ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
    }

    /**
     * Called by the SCWS to allow a servlet to handle a DELETE request.
     * <p>
     * If the HTTP DELETE request is incorrectly formatted, doDelete returns an
     * HTTP "Bad Request" message.
     * </p>
     * 
     * @param req
     *            - the HttpRequest object that contains the request the client
     *            made of the servlet
     * @param resp
     *            - the HttpResponse object that contains the response the
     *            servlet returns to the client
     * @throws ScwsException
     *             - if the request for the DELETE cannot be handled
     */
    public void doDelete(HttpRequest req, HttpResponse resp)
            throws ScwsException {
    }

    /**
     * Called by SCWS to allow a servlet to handle a GET request.
     * <p>
     * When using HTTP 1.1 chunked encoding (which means that the response has a
     * Transfer-Encoding header), do not set the Content-Length header.
     * </p>
     * If the HTTP POST request is incorrectly formatted, doPost returns an HTTP
     * "Bad Request" message.
     * 
     * @param req
     *            - an HttpRequest object that contains the request the client
     *            has made of the servlet
     * @param resp
     *            - an HttpResponse object that contains the response the
     *            servlet sends to the client
     * @throws ScwsException
     *             - if the request for the GET could not be handled
     */
    public void doGet(HttpRequest req, HttpResponse resp) throws ScwsException {
        // TODO Replace your doGet method below
        // ----------------------------------------------------------------------
        // return on response body:
        // status: 200 (OK)
        // Content Type: text/html
        // Protocol: HTTP/1.1
        // Method: GET
        // Header User Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)
        // Content Type: text/html
        // ...
        try {
            short httpVersion = req.getRequestHttpVersion();

            // set header
            resp.writeStatusCode(ScwsConstants.SC_OK);
            resp.setContentType(ScwsConstants.CONTENT_TYPE_TEXT_HTML);
            resp.enableChunkMode();

            // write body
            resp.appendContent(HELLO, (short) 0, (short) HELLO.length);
            
        } catch (Exception e) {
            resp.writeStatusCode(ScwsConstants.SC_BAD_REQUEST);
        }

        resp.flush();

    }

    /**
     * Receives an HTTP HEAD request from the protected service method and
     * handles the request.
     * <p>
     * The client sends a HEAD request when it wants to see only the headers of
     * a response, such as <tt>Content-Type</tt> or <tt>Content-Length</tt>.<br>
     * <p>
     * The HTTP HEAD method counts the output bytes in the response to set the
     * Content-Length header accurately.
     * <p>
     * If the HTTP HEAD request is incorrectly formatted, doHead returns an HTTP
     * "Bad Request" message.
     * 
     * @param req
     *            - the request object that is passed to the servlet
     * @param resp
     *            - the response object that the servlet uses to return the
     *            headers to the clien
     * @throws ScwsException
     *             - if the request for the HEAD could not be handled
     */
    public void doHead(HttpRequest req, HttpResponse resp) throws ScwsException {

    }

    /**
     * The OPTIONS method represents a request for information about the
     * communication options available on the request/response chain identified
     * by the Servlet.
     * <p>
     * Called by the SCWS to allow a servlet to handle a OPTIONS request. <br>
     * The OPTIONS request determines which HTTP methods the server supports and
     * returns an appropriate header.<br>
     * For example, if a servlet overrides doGet, this method returns the
     * following header:
     * 
     * <pre>
     * Allow: <tt>GET</tt>, <tt>HEAD</tt>, <tt>OPTIONS</tt>
     * </pre>
     * 
     * </p>
     * 
     * @param req
     *            - the HttpRequest object that contains the request the client
     *            made of the servlet
     * @param resp
     *            - the HttpResponse object that contains the response the
     *            servlet returns to the client
     * @throws ScwsException
     *             - if the request for the OPTIONS cannot be handled
     */
    public void doOptions(HttpRequest req, HttpResponse resp)
            throws ScwsException {
        resp.appendHeaderVariable(ALLOW, (short) 0, (short) ALLOW.length,
                ALLOW_OPTIONS, (short) 0, (short) ALLOW_OPTIONS.length);
        resp.flush();

    }

    /**
     * Called by the SCWS to allow the servlet to handle a POST request.
     * <p>
     * When using HTTP 1.1 chunked encoding (which means that the response has a
     * Transfer-Encoding header), do not set the Content-Length header.
     * </p>
     * If the HTTP POST request is incorrectly formatted, doPost returns an HTTP
     * "Bad Request" message.
     * 
     * @param req
     *            - an HttpRequest object that contains the request the client
     *            has made of the servlet
     * @param resp
     *            - an HttpResponse object that contains the response the
     *            servlet sends to the client
     * @throws ScwsException
     *             - if the request for the POST could not be handled
     */
    public void doPost(HttpRequest req, HttpResponse resp) throws ScwsException {
    }

    /**
     * Called by the SCWS (via the service method) to allow a servlet to handle
     * a PUT request.
     * <p>
     * If the HTTP PUT request is incorrectly formatted, doPut returns an HTTP
     * "Bad Request" message.
     * 
     * @param req
     *            - the HttpRequest object that contains the request the client
     *            made of the servlet
     * @param resp
     *            - the HttpResponse object that contains the response the
     *            servlet returns to the client
     * @throws HttpCardServletException
     *             - if the request for the PUT cannot be handled
     * @throws java.lang.Exception
     */
    public void doPut(HttpRequest req, HttpResponse resp) throws ScwsException {
    }

    /**
     * Called by the server (via the service method) to allow a servlet to
     * handle a TRACE request.
     * <p>
     * The TRACE method is used to invoke a remote, application-layer loop-back
     * of the request message.
     * </p>
     * 
     * @param req
     *            - the HttpRequest object that contains the request the client
     *            made of the servlet
     * @param resp
     *            - the HttpResponse object that contains the response the
     *            servlet returns to the client
     * @throws ScwsException
     *             - if the request for the TRACE cannot be handled
     */
    public void doTrace(HttpRequest req, HttpResponse resp)
            throws ScwsException {
        // TODO Auto-generated method stub
        // --------------------------
        /*
         * A TRACE returns the headers sent with the TRACE request to the
         * client, so that they can be used in debugging.
         */
        try {

            // set header
            resp.writeStatusCode(ScwsConstants.SC_OK);
            resp.setContentType(ScwsConstants.CONTENT_TYPE_TEXT_PLAIN);
            resp.enableChunkMode();

            // TRACE /MyServlet1 HTTP/1.1
            resp.appendContent(TRACE, (short) 0, (short) TRACE.length);
            resp.appendContent(SPACE, (short) 0, (short) SPACE.length);
            getAndOutputContent(req, resp, ScwsConstants.URI_PATH_TAG);

            resp.appendContent(SPACE, (short) 0, (short) SPACE.length);
            short httpVersion = req.getRequestHttpVersion();
            if (httpVersion == ScwsConstants.HTTP_PROTOCOL_VERSION_10)
                resp.appendContent(HTTP_10, (short) 0, (short) HTTP_10.length);
            else
                resp.appendContent(HTTP_11, (short) 0, (short) HTTP_11.length);
            resp.appendContent(CRLF, (short) 0, (short) CRLF.length);

            // ex: Accept: */*
            resp.appendContent(ACCEPT, (short) 0, (short) ACCEPT.length);
            getAndOutputContent(req, resp, ScwsConstants.HEADER_ACCEPT);
            resp.appendContent(CRLF, (short) 0, (short) CRLF.length);

            // ex: Accept-Language: en-us
            resp.appendContent(ACCEPT_LANG, (short) 0,
                    (short) ACCEPT_LANG.length);
            getAndOutputContent(req, resp, ScwsConstants.HEADER_ACCEPT_LANGUAGE);
            resp.appendContent(CRLF, (short) 0, (short) CRLF.length);

            // ex: Host: localhost
            resp.appendContent(HOST, (short) 0, (short) HOST.length);
            getAndOutputContent(req, resp, ScwsConstants.HEADER_HOST);
            resp.appendContent(CRLF, (short) 0, (short) CRLF.length);

            // ex: User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT
            // 5.0)
            resp
                    .appendContent(USER_AGENT, (short) 0,
                            (short) USER_AGENT.length);
            getAndOutputContent(req, resp, ScwsConstants.HEADER_USER_AGENT);
            resp.appendContent(CRLF, (short) 0, (short) CRLF.length);

            // ex: Accept-Encoding: gzip, deflate
            resp.appendContent(ACCEPT_ENCODING, (short) 0,
                    (short) ACCEPT_ENCODING.length);
            getAndOutputContent(req, resp, ScwsConstants.HEADER_ACCEPT_ENCODING);
            resp.appendContent(CRLF, (short) 0, (short) CRLF.length);

        } catch (Exception e) {
            resp.writeStatusCode(ScwsConstants.SC_NOT_IMPLEMENTED);
        }

        resp.flush();

    }

    private void getAndOutputContent(HttpRequest req, HttpResponse resp,
            short keyword) {
        short contentLgth = req.findAndCopyKeywordValue(keyword,
                temporaryBuffer, (short) 1,
                (short) (temporaryBuffer.length - 1));
        if (contentLgth == ScwsConstants.KEYWORD_NOT_FOUND)
            contentLgth++; // Query string may not exist, so set it 0 if did
        temporaryBuffer[0] = (byte) (contentLgth);
        resp.appendContent(temporaryBuffer, (short) 1,
                (short) temporaryBuffer[0]);
        clearBuffer(); // Clear buffer for reuse
    }

    private void clearBuffer() {
        for (short i = 0; i < temporaryBuffer.length; i++) {
            temporaryBuffer[i] = 0;
        }
    }

    private short getLength(byte[] data) {
        for (short i = 0; i < data.length; i++) {
            if (data[i] == 0) {
                return i;
            }
        }
        return 0;
    }
}