001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package com.sun.midp.io.j2me.apdu;
028:
029: import com.sun.cardreader.*;
030: import java.io.IOException;
031: import com.sun.midp.security.*;
032: import java.util.*;
033: import java.io.*;
034: import com.sun.midp.main.Configuration;
035:
036: /**
037: * This class provides virtual device driving the simulator.
038: */
039: public class Simulator extends com.sun.cardreader.CardDevice {
040: /** Configuration property name. */
041: public static final String PROP_NAME = "com.sun.midp.io.j2me.apdu.hostsandports";
042: /** Host names. */
043: private static String[] hosts = new String[0];
044:
045: /** Port numbers. */
046: private static int[] ports = new int[0];
047:
048: /** Input streams. */
049: private InputStream[] in = new InputStream[0];
050:
051: /** Output streams. */
052: private OutputStream[] out = new OutputStream[0];
053:
054: /**
055: * Socket connection to connect with the Java Card reference
056: * implementation.
057: */
058: private com.sun.midp.io.j2me.socket.Protocol[] conn = new com.sun.midp.io.j2me.socket.Protocol[0];
059:
060: /**
061: * Number of slots supported by the simulator.
062: */
063: private int slotCount = 0;
064:
065: /**
066: * Number of the current slot for operations.
067: */
068: private int currentSlot = 0;
069:
070: /**
071: * Is the card changed since last reset.
072: */
073: private boolean cardChanged;
074:
075: /** The response message received from the card. */
076: private TLP224Message responseMsg;
077:
078: /** Message sent to the card. */
079: private TLP224Message commandMsg;
080:
081: /** Byte array containing current APDU header. */
082: private byte[] header;
083:
084: /** Current command APDU data. */
085: private byte[] command;
086:
087: /** Lc value for the current APDU (data size). */
088: private int Lc;
089:
090: /** Le value for the current APDU (length of expected data). */
091: private int Le;
092:
093: /** 1st byte of APDU status word. */
094: private int sw1;
095:
096: /** 2nd byte of APDU status word. */
097: private int sw2;
098:
099: /** Data received from the card during APDU exchange. */
100: private byte[] output;
101:
102: /** Current size of received data. */
103: private int dataSize;
104:
105: /**
106: * Initializes the device.
107: *
108: * @throws IOException If device initialization failed.
109: * @throws CardDeviceException If device configuration is bad.
110: */
111: public void init() throws IOException, CardDeviceException {
112:
113: /* Read configuration, moved from Cad */
114: String config = Configuration.getProperty(PROP_NAME);
115:
116: boolean ok;
117: try {
118: Vector list = new Vector();
119:
120: int index = config.indexOf(",");
121: while (index != -1) {
122: list.addElement(config.substring(0, index));
123: config = config.substring(index + 1);
124: index = config.indexOf(",");
125: }
126: list.addElement(config);
127:
128: this .slotCount = list.size();
129: this .hosts = new String[this .slotCount];
130: this .ports = new int[this .slotCount];
131: this .in = new InputStream[this .slotCount];
132: this .out = new OutputStream[this .slotCount];
133: this .conn = new com.sun.midp.io.j2me.socket.Protocol[this .slotCount];
134:
135: for (int i = 0; i < list.size(); i++) {
136: String s = (String) list.elementAt(i);
137: index = s.indexOf(":");
138: hosts[i] = s.substring(0, index++).trim();
139: ports[i] = Integer.parseInt(s.substring(index));
140: }
141: ok = list.size() > 0;
142: } catch (NullPointerException npe) {
143: ok = false;
144: } catch (IndexOutOfBoundsException iobe) {
145: ok = false;
146: } catch (NumberFormatException nfe) {
147: ok = false;
148: }
149:
150: if (!ok) {
151: throw new CardDeviceException(
152: "Simulator configuration is bad");
153: }
154: header = new byte[4];
155: commandMsg = new TLP224Message();
156: responseMsg = new TLP224Message();
157: cardChanged = false;
158: }
159:
160: /**
161: * Close the specified slot.
162: * @param slot Slot number
163: * @throws IOException If slot close failed.
164: */
165: public void closeSlot(int slot) throws IOException {
166: String msg = "";
167: if (slot >= 0 && slot <= this .slotCount) {
168:
169: try {
170: if (this .in[slot] != null) {
171: InputStream tmp = this .in[slot];
172: this .in[slot] = null;
173: tmp.close();
174: }
175: } catch (IOException e) {
176: msg += e.getMessage();
177: }
178:
179: try {
180: if (this .out[slot] != null) {
181: OutputStream tmp = this .out[slot];
182: this .out[slot] = null;
183: tmp.close();
184: }
185: } catch (IOException e) {
186: msg += e.getMessage();
187: }
188:
189: try {
190: if (this .conn[slot] != null) {
191: com.sun.midp.io.j2me.socket.Protocol tmp = this .conn[slot];
192: this .conn[slot] = null;
193: tmp.close();
194: }
195: } catch (IOException e) {
196: msg += e.getMessage();
197: }
198: if (msg.length() != 0) {
199: throw new IOException(msg);
200: }
201: } else {
202: throw new IOException("Invalid slot number");
203: }
204: }
205:
206: /**
207: * Closes the device.
208: * No exceptions thrown to avoid mess in exception handlers trying
209: * to clean up and close the device.
210: */
211: public void close() {
212: for (int i = 0; i < this .slotCount; i++) {
213: try {
214: closeSlot(i);
215: } catch (IOException e) { // ignored
216: }
217: }
218: }
219:
220: /**
221: * Performs platform lock of the device. This is intended to make
222: * sure that no other native application
223: * uses the same device during a transaction.
224: *
225: * @throws IOException If a device locking failed.
226: */
227: public void lock() throws IOException {
228: }
229:
230: /**
231: * Unlocks the device.
232: *
233: * @throws IOException If a device unlocking failed.
234: */
235: public void unlock() throws IOException {
236: }
237:
238: /**
239: * Open the specified slot.
240: *
241: * @param slot Slot number
242: * @param token Security token
243: * @throws IOException If slot opening failed.
244: */
245: public void openSlot(int slot, SecurityToken token)
246: throws IOException {
247: // IMPL_NOTE: The CREF starts very slow. TCK's port checking is not enough:
248: // the CREF can accept connection a little later than port
249: // is not available. Two seconds is enough on my computer
250: try {
251: Thread.sleep(2000);
252: } catch (InterruptedException ie) {
253: } // ignored
254:
255: // Here we open socket to the simulator
256: this .conn[slot] = new com.sun.midp.io.j2me.socket.Protocol();
257: String url = "//" + this .hosts[slot] + ":" + this .ports[slot];
258: try {
259: this .conn[slot].openPrim(token, url);
260: this .in[slot] = this .conn[slot].openInputStream();
261: this .out[slot] = this .conn[slot].openOutputStream();
262: } catch (IOException ie) {
263: throw new IOException("Cannot open '" + url + "':" + ie);
264: }
265: }
266:
267: /**
268: * Selects the current slot for the subsequent transfer operations.
269: * For the one-slot devices the default slot number is 0.
270: *
271: * @param slot Slot number
272: * @throws IOException If a slot selection failed.
273: */
274: public void selectSlot(int slot) throws IOException {
275: if (this .conn[slot] == null) {
276: throw new IOException("Connection for simulator slot "
277: + Integer.toString(slot) + " not open");
278: }
279:
280: this .currentSlot = slot;
281: cardChanged = false;
282: }
283:
284: /**
285: * Checks if this slot is SAT slot.
286: *
287: * @param slotNumber Slot number
288: * @return <code>true</code> if the slot is dedicated for SAT,
289: * <code>false</code> otherwise
290: * @throws IOException If an error occured.
291: */
292: public boolean isSatSlot(int slotNumber) throws IOException {
293: // IMPL_NOTE: This method must return always false if SAT support is disabled
294: return slotNumber == 0;
295: }
296:
297: /**
298: * Performs reset of device.
299: * Returns ATR into provided buffer
300: *
301: * @param atr Buffer for ATR bytes
302: * @return Length of ATR
303: * @throws IOException If a reset failed.
304: */
305: public int cmdReset(byte[] atr) throws IOException {
306: byte[] data = commandMsg.getData();
307: int atrLen;
308:
309: data[0] = TLP224Message.ACK;
310: data[1] = 4;
311: data[2] = TLP224Message.POWER_UP;
312: data[3] = 0;
313: data[4] = 0;
314: data[5] = 0;
315: data[6] = commandMsg.computeLRC(6);
316: commandMsg.setLength(7);
317: sendTLP224Message(commandMsg);
318: receiveTLP224Message(responseMsg);
319:
320: data = responseMsg.getData();
321: if (data[2] == TLP224Message.STATUS_CARD_REMOVED) {
322: // the card has been just inserted, try again
323: sendTLP224Message(commandMsg);
324: receiveTLP224Message(responseMsg);
325: }
326: if (data[2] != 0) {
327: throw new IOException("TLP224Error " + (data[2] & 0xff));
328: }
329: if (data[1] < 5 || (data[1] == 1 && data[2] == 0)) {
330: throw new IOException("CREF is not ready");
331: }
332: // card on-line
333: atrLen = data[5];
334: if (atrLen < 0 || atrLen > atr.length) {
335: throw new IOException("Invalid length of ATR (" + atrLen
336: + ")");
337: }
338: System.arraycopy(data, 6, atr, 0, atrLen);
339:
340: return atrLen;
341: }
342:
343: /**
344: * Performs 'POWER DOWN' command.
345: *
346: * @throws IOException If a reset failed.
347: */
348: public void cmdPowerDown() throws IOException {
349: byte[] data = commandMsg.getData();
350: int atrLen;
351:
352: data[0] = TLP224Message.ACK;
353: data[1] = 1;
354: data[2] = 0x4D; // 'POWER_DOWN'
355: data[3] = commandMsg.computeLRC(3);
356: commandMsg.setLength(4);
357: sendTLP224Message(commandMsg);
358: receiveTLP224Message(responseMsg);
359: }
360:
361: /**
362: * Checks if the card in the selected slot was changed
363: * since last call or since last reset.
364: * Always called after any transfer operation, so
365: * implementation can check and store that status during
366: * this operation.
367: *
368: * @return true if was changed, false otherwise.
369: * @throws IOException If something fails.
370: */
371: public boolean isCardChanged() throws IOException {
372: boolean retValue = cardChanged;
373: cardChanged = false;
374: return retValue;
375: }
376:
377: /**
378: * Performs data transfer to the device.
379: *
380: * @param commandAPDU Request bytes
381: * @param response Response bytes
382: * @return Length of response
383: * @throws IOException If a data transfer failed.
384: */
385: public int cmdXfer(byte[] commandAPDU, byte[] response)
386: throws IOException {
387:
388: System.arraycopy(commandAPDU, 0, header, 0, 4);
389: command = commandAPDU;
390: dataSize = 0;
391:
392: if (command.length == 4) { // case 1 -> ISO_IN
393: Lc = 0;
394: Le = -1;
395: isoIn();
396: } else if (command.length == 5) { // case 2 -> ISO_OUT
397: Lc = 0;
398: Le = command[4] & 0xFF;
399: isoOut();
400: } else if (command.length == (command[4] & 0xFF) + 5) { // case 3 -> ISO_IN
401: Lc = (command[4] & 0xFF);
402: Le = -1;
403: isoIn();
404: } else if (command.length > (command[4] & 0xFF) + 5) { // case 4 -> ISO_IN
405: Lc = (command[4] & 0xFF);
406: Le = (command[Lc + 5] & 0xFF);
407: isoIn();
408: } else {
409: throw new IOException("Malformed APDU");
410: }
411: if (dataSize > 0) {
412: System.arraycopy(output, 0, response, 0, dataSize);
413: }
414: response[dataSize] = (byte) sw1;
415: response[dataSize + 1] = (byte) sw2;
416:
417: return dataSize + 2;
418: }
419:
420: /**
421: * Format and send an ISO_IN command to the CAD.
422: * @exception InterruptedIOException if connection was closed in the
423: * other thread
424: * @exception IOException if a communication error happens
425: */
426: private void isoIn() throws IOException {
427:
428: byte[] data = commandMsg.getData();
429: int length = Lc;
430:
431: data[0] = TLP224Message.ACK;
432: data[1] = (byte) (length + 6);
433: data[2] = TLP224Message.ISO_INPUT;
434: System.arraycopy(header, 0, data, 3, 4);
435: data[7] = (byte) length;
436: if (length > 0)
437: System.arraycopy(command, 5, data, 8, length);
438: data[length + 8] = commandMsg.computeLRC(length + 8);
439: commandMsg.setLength(length + 9);
440:
441: sendTLP224Message(commandMsg);
442: receiveTLP224Message(responseMsg);
443: data = responseMsg.getData();
444: sw1 = data[3] & 0xff;
445: sw2 = data[4] & 0xff;
446:
447: byte status = data[2];
448: if (status != TLP224Message.STATUS_SUCCESS
449: && status != TLP224Message.STATUS_CARD_ERROR
450: && status != TLP224Message.STATUS_INTERRUPTED_EXCHANGE) {
451: throw new IOException("TLP224Error " + status);
452: }
453: }
454:
455: /**
456: * Format and send an ISO_OUT command to the CAD.
457: * @exception InterruptedIOException if connection was closed in the
458: * other thread
459: * @exception IOException if a communication error happens
460: */
461: private void isoOut() throws IOException {
462:
463: byte[] data = commandMsg.getData();
464: data[0] = TLP224Message.ACK;
465: data[1] = 6;
466: data[2] = TLP224Message.ISO_OUTPUT;
467: System.arraycopy(header, 0, data, 3, 4);
468: data[7] = (byte) Le;
469: data[8] = commandMsg.computeLRC(8);
470: commandMsg.setLength(9);
471:
472: sendTLP224Message(commandMsg);
473: receiveTLP224Message(responseMsg);
474:
475: data = responseMsg.getData();
476:
477: int received = (data[1] == 3) ? 0 : responseMsg.getLength() - 6;
478:
479: if (received > 0) {
480: if (output == null || output.length < received) {
481: output = new byte[received];
482: }
483: System.arraycopy(data, 3, output, dataSize, received);
484: dataSize = received;
485: }
486:
487: sw1 = data[3 + received] & 0xff;
488: sw2 = data[4 + received] & 0xff;
489:
490: byte status = data[2];
491: if (status != TLP224Message.STATUS_SUCCESS
492: && status != TLP224Message.STATUS_CARD_ERROR
493: && status != TLP224Message.STATUS_INTERRUPTED_EXCHANGE) {
494: throw new IOException("TLP224Error " + status);
495: }
496: }
497:
498: /**
499: * Receive a TLP224 formatted message from the input stream.
500: * This method reads bytes from the input stream until an EOT (0x03)
501: * character is received. The resulting message is decoded and stored
502: * in the TL224Message msg. In the event of a transmission error, this
503: * method will send attempt error recovery by sending a TLP224 NACK
504: * message to the sender. Up to 5 retries will be performed.
505: * @param msg The object to store the received message in.
506: * @exception IOException If an error occurs while reading from the input
507: * stream.
508: */
509: private void receiveTLP224Message(TLP224Message msg)
510: throws IOException {
511:
512: byte[] data = msg.getData();
513: int tries = 0;
514:
515: try {
516: while (true) {
517: // Only retry link level errors 5 times before giving up.
518: if (tries++ > 5) {
519: throw new IOException("TLP224Error " + 1);
520: }
521:
522: // loop reading characters until EOT is received.
523: boolean messageTooLong = false;
524: boolean xmitError = false;
525: int got = 0;
526:
527: int hiNibble, lowNibble;
528:
529: while ((hiNibble = in[currentSlot].read()) != TLP224Message.EOT) {
530:
531: if (hiNibble == -1) {
532: throw new EOFException();
533: }
534:
535: if ((lowNibble = in[currentSlot].read()) == -1) {
536: throw new EOFException();
537: }
538:
539: xmitError |= (hiNibble < '0' || hiNibble > 'F' || (hiNibble > '9' && hiNibble < 'A'))
540: || (lowNibble < '0' || lowNibble > 'F' || (lowNibble > '9' && lowNibble < 'A'));
541:
542: if (lowNibble == TLP224Message.EOT) {
543: break;
544: }
545:
546: if (xmitError)
547: continue;
548:
549: hiNibble -= hiNibble > '9' ? 0x37 : 0x30;
550: lowNibble -= lowNibble > '9' ? 0x37 : 0x30;
551:
552: try {
553: data[got++] = (byte) ((hiNibble << 4) | lowNibble);
554: } catch (ArrayIndexOutOfBoundsException e) {
555: messageTooLong = true;
556: }
557: }
558:
559: if (xmitError || got < 3) {
560: transmissionError();
561: continue;
562: }
563:
564: if (messageTooLong) {
565: statusResponse(TLP224Message.STATUS_MESSAGE_TOO_LONG);
566: continue;
567: }
568:
569: if (data[got - 1] != msg.computeLRC(got - 1)
570: || data[1] != (byte) (got - 3)) {
571: // the message must contain a valid LRC, the second byte
572: // of the message is the command length. The total message
573: // length includes the ACK/NACK, the length and the LRC
574: transmissionError();
575: continue;
576: }
577:
578: // The first byte of the message must be either an ACK or a NACK
579: if (data[0] != TLP224Message.ACK
580: && data[0] != TLP224Message.NACK) {
581: statusResponse(TLP224Message.STATUS_PROTOCOL_ERROR);
582: continue;
583: }
584:
585: msg.setLength(got);
586: break;
587: }
588: } catch (IOException e) {
589: cardChanged = true;
590: throw e;
591: }
592: }
593:
594: /**
595: * Formats a TLP224Message into it's ASCII representation and
596: * sends the message on the output stream.
597: * @param msg TLP224 encoded message to be sent
598: * @exception IOException is thrown in case there are any problems.
599: */
600: private void sendTLP224Message(TLP224Message msg)
601: throws IOException {
602: byte[] data = msg.getData();
603:
604: try {
605: for (int i = 0; i < msg.getLength(); i++) {
606: int nibble = data[i] >> 4 & 0xf;
607: out[currentSlot].write(nibble
608: + (nibble < 10 ? 0x30 : 0x37));
609: nibble = data[i] & 0xf;
610: out[currentSlot].write(nibble
611: + (nibble < 10 ? 0x30 : 0x37));
612: }
613:
614: out[currentSlot].write(TLP224Message.EOT);
615: out[currentSlot].flush();
616: } catch (IOException e) {
617: cardChanged = true;
618: throw e;
619: }
620: }
621:
622: /**
623: * Send a one byte TLP224 status response.
624: * @param code the status response
625: * @throws IOException if IO error occurs
626: */
627: private void statusResponse(int code) throws IOException {
628: TLP224Message msg = new TLP224Message();
629: byte[] data = msg.getData();
630: data[0] = TLP224Message.ACK;
631: data[1] = 1;
632: data[2] = (byte) code;
633: data[3] = msg.computeLRC(3);
634: msg.setLength(4);
635: sendTLP224Message(msg);
636: }
637:
638: /**
639: * Send a TLP224 Transmission Error response.
640: * @throws IOException if IO error occurs
641: */
642: private void transmissionError() throws IOException {
643: TLP224Message msg = new TLP224Message();
644: byte[] data = msg.getData();
645: data[0] = TLP224Message.NACK;
646: data[1] = 0;
647: data[2] = msg.computeLRC(2);
648: msg.setLength(3);
649: sendTLP224Message(msg);
650: }
651:
652: /**
653: * Gets number of slots on a device. Default implementation returns 1 which
654: * is ok for most devices.
655: *
656: * @return Number of slots on a device
657: */
658: public int getSlotCount() {
659: return this .slotCount;
660: }
661:
662: /*
663: * DEBUG: delete till end of file after debugging
664: * /
665: /**
666: * Formates byte array as hex string.
667: * @param buf array to format
668: * @param offset start offset in buffer
669: * @param length length of array
670: * @return result string
671: * /
672: public static String bytesToHex(byte[] buf, int offset, int length) {
673: StringBuffer sb = new StringBuffer();
674:
675: for (int i = 0; i < length; i++) {
676: int val = (buf[offset + i] & 0xFF);
677: if (i != 0) {
678: sb.append(' ');
679: }
680: sb.append(hexDigit(val >> 4));
681: sb.append(hexDigit(val & 0x0F));
682: }
683: return new String(sb);
684: }
685:
686: /**
687: * Converts integer value (0-16) into hex representation.
688: * @param val value to be converted
689: * @return result hex char
690: * /
691: private static char hexDigit(int val) {
692: val &= 0x0F;
693: if (val <= 9) {
694: return (char)(val + (int)'0');
695: }
696: return (char)(val - 0x0A + (int)'A');
697: }
698:
699: /**
700: * Calculates length of APDU. (For printing purpose.)
701: * @param apdu formatted APDU
702: * @return length of APDU command
703: * /
704: public static int getAPDULength(byte[] apdu) {
705: if (apdu == null) {
706: return 0;
707: }
708: if (apdu.length > 5) {
709: int Lc = apdu[4] & 0xFF;
710: if (apdu.length == Lc + 5) {
711: return Lc + 5;
712: } else
713: if (apdu.length > Lc + 5) {
714: return Lc + 6;
715: } else {
716: return apdu.length;
717: }
718: } else {
719: return apdu.length;
720: }
721: }
722: /* */
723: }
|