
My former colleague wrote very interesting post about interaction of PoS terminal and banking cards with chip. So sharing with my readers. Enjoy and peace on you!
Just notes, remarks, tips & tricks about programming, GSM, JavaCard etc.

pip install pygments
export LESS="-R"
export LESSOPEN="|pygmentize -g %s"
/** * Constructor of the applet */ public otp() { // Get the reference of the applet ToolkitRegistry object reg = ToolkitRegistry.getEntry (); menuGetNextPasswd = new byte[] { (byte) 'G', (byte) 'e', (byte) 't', (byte) ' ', (byte) 'n', (byte) 'e', (byte) 'x', (byte) 't', (byte) ' ', (byte) 'p', (byte) 'a', (byte) 's', (byte) 's', (byte) 'w', (byte) 'o', (byte) 'r', (byte) 'd' }; menuInitKey = new byte[] { (byte) 'I', (byte) 'n', (byte) 'i', (byte) 't', (byte) ' ', (byte) 'k', (byte) 'e', (byte) 'y' }; // Define the applet Menu Entry idMenuGetNextPasswd = reg .initMenuEntry (menuGetNextPasswd, (short) 0, (short) menuGetNextPasswd.length, PRO_CMD_SELECT_ITEM, false, (byte) 0, (short) 0); idMenuInitKey = reg .initMenuEntry (menuInitKey, (short) 0, (short) menuInitKey.length, (byte) 0, false, (byte) 0, (short) 0); // during instantiation of the applet key isn't initialized yet isKeyInitialized = false; // fixing key length to 20 bytes key = new byte[KEY_LENGTH]; tmpBuf = new byte[KEY_LENGTH_ASCII]; }
public void processToolkit(byte event) { EnvelopeHandler envHdlr = EnvelopeHandler.getTheHandler (); // Manage the request following the MENU SELECTION event type if (event == EVENT_MENU_SELECTION) { // Get the selected item byte selectedItemId = envHdlr.getItemIdentifier (); // Perform the required service following the menuGetNextPasswd // selected item if (selectedItemId == idMenuGetNextPasswd) { getNextPasswd (); } // Perform the required service following the menuInitKey selected // item if (selectedItemId == idMenuInitKey) { initKey (); } } }
/** * Manage the menuGetNextPasswd selection */ private void getNextPasswd() { if (isKeyInitialized) { try { hash = MessageDigest.getInstance (MessageDigest.ALG_SHA, true); hash.doFinal (key, (short) 0, (short) key.length , tmpBuf, (short) 0); Util.arrayCopyNonAtomic (tmpBuf, (short) 0, key, (short) 0, (short) key.length); displayHexBuffer (key, (short) key.length); } catch (CryptoException e) { } } else { displayText(msgKeyNotInited); } return; }
(def md (MessageDigest/getInstance "SHA-1")) (defn ascii2hex [ascii-str] (map #(Integer/parseInt % 16) (map #(apply str %) (partition 2 ascii-str)))) (defn bytes2hexStr [bytes] (apply str (map #(.toUpperCase %) (map #(format "%02x" %) (map #(bit-and 0xFF %) (seq bytes)))))) (defn get-next-password [key-str] (. md reset) (bytes2hexStr (seq (. md digest (into-array Byte/TYPE (map #(.byteValue %) (ascii2hex key-str)))))))
java -jar <jarfilename>I hope it will be more useful.
Connection type SMTP connections.
This may be either nil (possibly upgraded to STARTTLS if possible), or `starttls' (refuse to send if STARTTLS isn't available), or `plain' (never use STARTTLS)..
:type specifies the connection type, one of the following:Also I found that we can use port 465 instead of 587 with smtp.gmail.com. After changing my configuration in .gnus to the following I can send emails from gnus again (hooray!!!):
nil or `network'
-- Begin with an ordinary network connection, and if
the parameters :success and :capability-command
are also supplied, try to upgrade to an encrypted
connection via STARTTLS. Even if that
fails (e.g. if HOST does not support TLS), retain
an unencrypted connection.
`plain' -- An ordinary, unencrypted network connection.
`starttls' -- Begin with an ordinary connection, and try
upgrading via STARTTLS. If that fails for any
reason, drop the connection; in that case the
returned object is a killed process.
`tls' -- A TLS connection.
`ssl' -- Equivalent to `tls'.
`shell' -- A shell connection.
(setq message-send-mail-function 'smtpmail-send-it
smtpmail-starttls-credentials '(("smtp.gmail.com" 465 nil nil))
smtpmail-auth-credentials '(("smtp.gmail.com" 465 "name@gmail.com" nil))
smtpmail-default-smtp-server "smtp.gmail.com"
smtpmail-smtp-server "smtp.gmail.com"
smtpmail-smtp-service 465
; smtpmail-debug-verb t
; smtpmail-debug-info tsmtpmail-local-domain nil
smtpmail-stream-type 'ssl)
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); } } }
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 } } }
| 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 gsmFilesspush 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; } |
while(true) {
gsmFile.readBinary(fileOffset
, plmn
, (short) 0
, (short) plmn.length);
fileOffset += plmn.length;
}
.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:
| GET | Requests the specified resource |
| HEAD | Requests meta-information on a resource (no body) |
| PUT | Uploads (add or replace) a resource on the server |
| POST | Submits data to be processed to the identified resource |
| DELETE | Deletes the specified resource on the server |
| OPTIONS | Returns the HTTP methods supported for the specified URI (debug purpose) |
| TRACE | Echos back the received requests (debug purposes) |
import uicc.scws.*;
class ServletHelloWorld extends Applet implements AppletEvent, ScwsExtension
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; } }