1 /**
  2  *  ---------
  3  * |.##> <##.|  Open Smart Card Development Platform (www.openscdp.org)
  4  * |#       #|  
  5  * |#       #|  Copyright (c) 1999-2009 CardContact Software & System Consulting
  6  * |'##> <##'|  Andreas Schwier, 32429 Minden, Germany (www.cardcontact.de)
  7  *  --------- 
  8  *
  9  *  This file is part of OpenSCDP.
 10  *
 11  *  OpenSCDP is free software; you can redistribute it and/or modify
 12  *  it under the terms of the GNU General Public License version 2 as
 13  *  published by the Free Software Foundation.
 14  *
 15  *  OpenSCDP is distributed in the hope that it will be useful,
 16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 18  *  GNU General Public License for more details.
 19  *
 20  *  You should have received a copy of the GNU General Public License
 21  *  along with OpenSCDP; if not, write to the Free Software
 22  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 23  *
 24  * @fileoverview Implementation of ISO 7816-4 APDU processing
 25  */
 26 
 27 
 28 
 29 /**
 30  * Create an APDU
 31  * 
 32  * <p>This constructor supports the signatures</p>
 33  * <ul>
 34  *  <li>APDU(ByteString command)</li>
 35  *  <li>APDU(Number cla, Number ins, Number p1, Number p2)</li>
 36  *  <li>APDU(Number cla, Number ins, Number p1, Number p2, data)</li>
 37  *  <li>APDU(Number cla, Number ins, Number p1, Number p2, data, Ne)</li>
 38  * </ul>
 39  * @class Class implementing support for command and response APDUs
 40  * @constructor
 41  * @param {ByteString} command the command APDU
 42  * @param {Number} cla the class byte
 43  * @param {Number} ins the instruction byte
 44  * @param {Number} p1 the first parameter
 45  * @param {Number} p2 the second parameter
 46  * @param {ByteString} data the data field (optional)
 47  * @param {Number} Ne the number of expected bytes (optional)
 48  */
 49 function APDU() {
 50 	if (arguments.length > 0) {
 51 		var arg = arguments[0];
 52 		if (arg instanceof ByteString) {
 53 			if (arguments.length != 1) {
 54 				throw new GPError("APDU", GPError.INVALID_ARGUMENTS, APDU.SW_GENERALERROR, "Only one argument of type ByteString expected");
 55 			}
 56 			this.fromByteString(arg);
 57 		} else {
 58 			if ((arguments.length < 4) || (arguments.length > 6)) {
 59 				throw new GPError("APDU", GPError.INVALID_ARGUMENTS, APDU.SW_GENERALERROR, "4 to 6 arguments expected");
 60 			}
 61 			
 62 			for (var i = 0; i < 4; i++) {
 63 				if (typeof(arguments[i]) != "number") {
 64 					throw new GPError("APDU", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Argument must be of type Number");
 65 				}
 66 			}
 67 			this.cla = arguments[0];
 68 			this.ins = arguments[1];
 69 			this.p1 = arguments[2];
 70 			this.p2 = arguments[3];
 71 
 72 			var i = 4;
 73 			if (arguments.length > i) {
 74 				if (arguments[i] instanceof ByteString) {
 75 					this.cdata = arguments[i];
 76 					i++;
 77 				}
 78 			}
 79 			
 80 			if (arguments.length > i) {
 81 				if (typeof(arguments[i]) != "number") {
 82 					throw new GPError("APDU", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Argument must be of type Number");
 83 				}
 84 				this.ne = arguments[i];
 85 			}
 86 		}
 87 	}
 88 	this.rapdu = null;
 89 	this.SW = APDU.SW_GENERALERROR;
 90 }
 91 
 92 APDU.INS_DEACTIVATE				= 0x04;
 93 APDU.INS_VERIFY					= 0x20;
 94 APDU.INS_MANAGE_SE				= 0x22;
 95 APDU.INS_CHANGE_REFERENCE_DATA	= 0x24;
 96 APDU.INS_PSO					= 0x2A;
 97 APDU.INS_RESET_RETRY_COUNTER	= 0x2C;
 98 APDU.INS_ACTIVATE				= 0x44;
 99 APDU.INS_GENERATE_KEY_PAIR		= 0x46;
100 APDU.INS_EXTERNAL_AUTHENTICATE	= 0x82;
101 APDU.INS_GET_CHALLENGE			= 0x84;
102 APDU.INS_GENERAL_AUTHENTICATE	= 0x86;
103 APDU.INS_COMPUTE_DIGITAL_SIGN	= 0x9E;
104 APDU.INS_SELECT					= 0xA4;
105 APDU.INS_READBINARY				= 0xB0;
106 APDU.INS_READ_BINARY			= 0xB0;
107 APDU.INS_READ_RECORD			= 0xB2;
108 APDU.INS_VERIFY_CERTIFICATE		= 0xBE;
109 APDU.INS_UPDATE_BINARY			= 0xD6;
110 APDU.INS_TERMINATE				= 0xE6;
111 
112 APDU.SW_OK                 = 0x9000;      	/* Process completed                 */
113 
114 APDU.SW_TIMEOUT            = 0x6401;      	/* Exec error: Command timeout       */
115 
116 APDU.SW_OKMOREDATA         = 0x6100;      	/*-Process completed, more data available*/
117 APDU.SW_WARNING            = 0x6200;      	/*-Warning: NV-Ram not changed       */
118 APDU.SW_WARNING1           = 0x6201;      	/*-Warning: NV-Ram not changed 1     */
119 APDU.SW_DATAINV            = 0x6281;      	/*-Warning: Part of data corrupted   */
120 APDU.SW_EOF                = 0x6282;      	/*-Warning: End of file reached      */
121 APDU.SW_INVFILE            = 0x6283;      	/* Warning: Invalidated file         */
122 APDU.SW_INVFORMAT          = 0x6284;      	/* Warning: Invalid file control     */
123 APDU.SW_WARNINGNVCHG       = 0x6300;      	/*-Warning: NV-Ram changed           */
124 APDU.SW_WARNINGCOUNT       = 0x63C0;      	/*-Warning: Warning with counter     */
125 APDU.SW_WARNING0LEFT       = 0x63C0;      	/*-Warning: Verify fail, no try left */
126 APDU.SW_WARNING1LEFT       = 0x63C1;      	/*-Warning: Verify fail, 1 try left  */
127 APDU.SW_WARNING2LEFT       = 0x63C2;      	/*-Warning: Verify fail, 2 tries left*/
128 APDU.SW_WARNING3LEFT       = 0x63C3;      	/*-Warning: Verify fail, 3 tries left*/
129 APDU.SW_EXECERR            = 0x6400;      	/*-Exec error: NV-Ram not changed    */
130 APDU.SW_MEMERR             = 0x6501;      	/*-Exec error: Memory failure        */
131 APDU.SW_MEMERRWRITE        = 0x6581;      	/*-Exec error: Memory failure        */
132 APDU.SW_WRONGLENGTH        = 0x6700;      	/*-Checking error: Wrong length      */
133 
134 APDU.SW_CLANOTSUPPORTED    = 0x6800;      	/*-Checking error: Function in CLA byte not supported */
135 APDU.SW_LCNOTSUPPORTED     = 0x6881;      	/*-Checking error: Logical channel not supported */
136 APDU.SW_SMNOTSUPPORTED     = 0x6882;      	/*-Checking error: Secure Messaging not supported */
137 APDU.SW_LASTCMDEXPECTED    = 0x6883;      	/*-Checking error: Last command of the chain expected */
138 APDU.SW_CHAINNOTSUPPORTED  = 0x6884;      	/*-Checking error: Command chaining not supported */
139 
140 APDU.SW_COMNOTALLOWED      = 0x6900;      	/*-Checking error: Command not allowed */
141 APDU.SW_COMINCOMPATIBLE    = 0x6981;      	/*-Checking error: Command incompatible with file structure */
142 APDU.SW_SECSTATNOTSAT      = 0x6982;      	/*-Checking error: Security condition not satisfied */
143 APDU.SW_AUTHMETHLOCKED     = 0x6983;      	/*-Checking error: Authentication method locked */
144 APDU.SW_REFDATANOTUSABLE   = 0x6984;      	/*-Checking error: Reference data not usable */
145 APDU.SW_CONDOFUSENOTSAT    = 0x6985;      	/*-Checking error: Condition of use not satisfied */
146 APDU.SW_COMNOTALLOWNOEF    = 0x6986;      	/*-Checking error: Command not allowed (no current EF) */
147 APDU.SW_SMOBJMISSING       = 0x6987;      	/*-Checking error: Expected secure messaging object missing */
148 APDU.SW_INCSMDATAOBJECT    = 0x6988;      	/*-Checking error: Incorrect secure messaging data object */
149 
150 APDU.SW_INVPARA            = 0x6A00;      	/*-Checking error: Wrong parameter P1-P2 */
151 APDU.SW_INVDATA            = 0x6A80;      	/*-Checking error: Incorrect parameter in the command data field*/
152 APDU.SW_FUNCNOTSUPPORTED   = 0x6A81;      	/*-Checking error: Function not supported */
153 APDU.SW_NOAPPL             = 0x6A82;      	/*-Checking error: File not found    */
154 APDU.SW_FILENOTFOUND       = 0x6A82;      	/*-Checking error: File not found    */
155 APDU.SW_RECORDNOTFOUND     = 0x6A83;      	/*-Checking error: Record not found    */
156 APDU.SW_OUTOFMEMORY        = 0x6A84;      	/*-Checking error: Not enough memory space in the file   */
157 APDU.SW_INVLCTLV           = 0x6A85;      	/*-Checking error: Nc inconsistent with TLV structure */
158 APDU.SW_INVACC             = 0x6A85;      	/*-Checking error: Access cond. n/f  */
159 APDU.SW_INCP1P2            = 0x6A86;      	/*-Checking error: Incorrect P1-P2   */
160 APDU.SW_INVLC              = 0x6A87;      	/*-Checking error: Lc inconsistent with P1-P2 */
161 APDU.SW_RDNOTFOUND         = 0x6A88;      	/*-Checking error: Reference data not found*/
162 APDU.SW_FILEEXISTS         = 0x6A89;      	/*-Checking error: File already exists */
163 APDU.SW_DFNAMEEXISTS       = 0x6A8A;      	/*-Checking error: DF name already exists */
164 
165 APDU.SW_INVP1P2            = 0x6B00;      	/*-Checking error: Wrong parameter P1-P2 */
166 APDU.SW_INVLE              = 0x6C00;      	/*-Checking error: Invalid Le        */
167 APDU.SW_INVINS             = 0x6D00;      	/*-Checking error: Wrong instruction */
168 APDU.SW_INVCLA             = 0x6E00;      	/*-Checking error: Class not supported */
169 APDU.SW_ACNOTSATISFIED     = 0x9804;      	/* Access conditions not satisfied   */
170 APDU.SW_NOMORESTORAGE      = 0x9210;      	/* No more storage available         */
171 APDU.SW_GENERALERROR       = 0x6F00;      	/*-Checking error: No precise diagnosis */
172 
173 
174 /**
175  * Create an APDU object from the encoded form (Called internally)
176  *
177  * @param {ByteString} bs
178  */
179 APDU.prototype.fromByteString = function(bs) {
180 	if (bs.length < 4) {
181 		throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_GENERALERROR, "Command APDU must be at least 4 bytes long");
182 	}
183 	this.cla = bs.byteAt(0);
184 	this.ins = bs.byteAt(1);
185 	this.p1 = bs.byteAt(2);
186 	this.p2 = bs.byteAt(3);
187 	
188 	if (bs.length > 4) {
189 		var extended = false;
190 		
191 		var i = 4;
192 		var l = bs.length - i;
193 		var n = bs.byteAt(i++);
194 		l--;
195 		
196 		if ((n == 0) && (l > 0)) {
197 			extended = true;
198 			if (l < 2) {
199 				throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Extended length APDU too short");
200 			}
201 			n = (bs.byteAt(i) << 8) + bs.byteAt(i + 1);
202 			i += 2;
203 			l -= 2;
204 		}
205 		
206 		if (l > 0) {	// Case 3s / Case 3e / Case 4s / Case 4e
207 			if (l < n) {
208 				throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Invalid Lc in APDU");
209 			}
210 			this.cdata = bs.bytes(i, n);
211 			i += n;
212 			l -= n;
213 			
214 			if (l > 0) {	// Case 4s / Case 4e
215 				n = bs.byteAt(i++);
216 				l--;
217 				if (extended) {
218 					if (l < 1) {
219 						throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Invalid Le in extended APDU");
220 					}
221 					n = (n << 8) + bs.byteAt(i++);
222 					l--;
223 				}
224 				this.ne = (extended && (n == 0) ? 65536 : n);
225 			}
226 		} else {
227 			this.ne = (extended && (n == 0) ? 65536 : n);
228 		}
229 		
230 		if (l > 0) {
231 			throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Too many bytes in APDU");
232 		}
233 	}
234 }
235 
236 
237 
238 /**
239  * Get encoded command APDU
240  *
241  * @type ByteString
242  * @return the encoded command APDU
243  */
244 APDU.prototype.getCommandAPDU = function() {
245 	var bb = new ByteBuffer();
246 	
247 	bb.append(this.cla);
248 	bb.append(this.ins);
249 	bb.append(this.p1);
250 	bb.append(this.p2);
251 
252 	var hasCData = (typeof(this.cdata) != "undefined");
253 	var hasNe = (typeof(this.ne) != "undefined");
254 	
255 	var extended = ((hasCData && this.cdata.length > 255) || 
256 					(hasNe && this.ne > 256));
257 
258 	if (extended) {
259 		bb.append(0);
260 	}
261 
262 	if (hasCData && this.cdata.length > 0) {
263 		if (extended) {
264 			bb.append(this.cdata.length >> 8);
265 		}
266 		bb.append(this.cdata.length & 0xFF);
267 		bb.append(this.cdata);
268 	}
269 	
270 	if (hasNe) {
271 		if (extended) {
272 			bb.append(this.ne >> 8);
273 		}
274 		bb.append(this.ne & 0xFF);
275 	}
276 	
277 	return bb.toByteString();
278 }
279 
280 
281 
282 /**
283  * Get encoded response APDU
284  *
285  * @type ByteString
286  * @return the encoded response APDU
287  */
288 APDU.prototype.getResponseAPDU = function() {
289 	var bb = new ByteBuffer();
290 	
291 	if (this.rdata) {
292 		bb.append(this.rdata);
293 	}
294 		
295 	bb.append(this.SW >> 8);
296 	bb.append(this.SW & 0xFF);
297 	
298 	return bb.toByteString();
299 }
300 
301 
302 
303 /**
304  * Gets the class byte
305  *
306  * @type Number
307  * @return the class byte
308  */
309 APDU.prototype.getCLA = function() {
310 	return this.cla;
311 }
312 
313 
314 
315 /**
316  * Test if command is an ISO command
317  *
318  * @type boolean
319  * @return true if command has ISO class byte
320  */
321 APDU.prototype.isISO = function() {
322 	return (this.cla & 0x80) == 0x00;
323 }
324 
325 
326 
327 /**
328  * Test if command chaining is indicated
329  *
330  * @type boolean
331  * @return true if chaining bit is set
332  */
333 APDU.prototype.isChained = function() {
334 	return (this.cla & 0x10) == 0x10;
335 }
336 
337 
338 
339 /**
340  * Test if command is send using secure messaging
341  *
342  * @type boolean
343  * @return true if secure messaging is indicated in CLA byte
344  */
345 APDU.prototype.isSecureMessaging = function() {
346 	return (this.cla & 0x08) == 0x08;
347 }
348 
349 
350 
351 /**
352  * Test if command is send using secure messaging
353  *
354  * @type boolean
355  * @return true if secure messaging is using an authenticated header
356  */
357 APDU.prototype.isAuthenticatedHeader = function() {
358 	return (this.cla & 0x0C) == 0x0C;
359 }
360 
361 
362 
363 /**
364  * Gets the instruction byte
365  *
366  * @type Number
367  * @return the instruction byte
368  */
369 APDU.prototype.getINS = function() {
370 	return this.ins;
371 }
372 
373 
374 
375 /**
376  * Gets the P1 byte
377  *
378  * @type Number
379  * @return the P1 byte
380  */
381 APDU.prototype.getP1 = function() {
382 	return this.p1;
383 }
384 
385 
386 
387 /**
388  * Gets the P2 byte
389  *
390  * @type Number
391  * @return the P2 byte
392  */
393 APDU.prototype.getP2 = function() {
394 	return this.p2;
395 }
396 
397 
398 
399 /**
400  * Set the command data
401  *
402  * @param {ByteString} cdata the command data
403  */
404 APDU.prototype.setCData = function(cdata) {
405 	this.cdata = cdata;
406 }
407 
408 
409 
410 /**
411  * Gets the command data
412  *
413  * @type ByteString
414  * @return the command data, if any else undefined
415  */
416 APDU.prototype.getCData = function() {
417 	return this.cdata;
418 }
419 
420 
421 
422 /**
423  * Check if APDU has command data
424  *
425  * @type boolean
426  * @return true if command APDU has data field
427  */
428 APDU.prototype.hasCData = function() {
429 	return ((typeof(this.cdata) != "undefined") && (this.cdata != null));
430 }
431 
432 
433 
434 /**
435  * Gets the command data as a list of TLV objects
436  *
437  * @type TLVList
438  * @return the command data as TLV list, if any else undefined
439  */
440 APDU.prototype.getCDataAsTLVList = function() {
441 	if (typeof(this.cdata) == "undefined") {
442 		throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_INVDATA, "No data in command APDU");
443 	}
444 	
445 	try	{
446 		var a = new TLVList(this.cdata, TLV.EMV);
447 	}
448 	catch(e) {
449 		throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_INVDATA, "Invalid TLV data in command APDU");
450 	}
451 
452 	return a;
453 }
454 
455 
456 
457 /**
458  * Gets the number of expected bytes
459  *
460  * @type Number
461  * @return the number of expected bytes or undefined 
462  */
463 APDU.prototype.getNe = function() {
464 	return this.ne;
465 }
466 
467 
468 
469 /**
470  * Check if APDU has Le field
471  *
472  * @type boolean
473  * @return true if command APDU has Le field
474  */
475 APDU.prototype.hasLe = function() {
476 	return typeof(this.ne) != "undefined";
477 }
478 
479 
480 
481 /**
482  * Set secure channel object to be used in wrap and unwrap methods
483  *
484  * @param {SecureChannel} secureChannel the channel
485  */
486 APDU.prototype.setSecureChannel = function(secureChannel) {
487 	this.secureChannel = secureChannel;
488 }
489 
490 
491 
492 /**
493  * Return the secure channel, if any
494  *
495  * @type SecureChannel
496  * @return the secure channel
497  */
498 APDU.prototype.getSecureChannel = function() {
499 	return this.secureChannel;
500 }
501 
502 
503 
504 /**
505  * Test if a secure channel is defined for this APDU
506  *
507  * @type boolean
508  * @return true, if secure channel is set
509  */
510 APDU.prototype.hasSecureChannel = function() {
511 	return (typeof(this.secureChannel) != "undefined") && (this.secureChannel != null);
512 }
513 
514 
515 
516 /**
517  * Wrap APDU using secure channel
518  */
519 APDU.prototype.wrap = function() {
520 	if (this.hasSecureChannel()) {
521 		this.secureChannel.wrap(this);
522 	}
523 }
524 
525 
526 
527 /**
528  * Unwrap APDU using secure channel
529  */
530 APDU.prototype.unwrap = function() {
531 	if (this.hasSecureChannel()) {
532 		this.secureChannel.unwrap(this);
533 	}
534 }
535 
536 
537 
538 /**
539  * Sets the response data field for the response APDU
540  *
541  * @param {ByteString} data the response data field
542  */
543 APDU.prototype.setRData = function(data) {
544 	this.rdata = data;
545 }
546 
547 
548 
549 /**
550  * Get the response data
551  *
552  * @type ByteString
553  * @return the response data
554  */
555 APDU.prototype.getRData = function() {
556 	return this.rdata;
557 }
558 
559 
560 
561 /**
562  * Check if APDU has response data
563  *
564  * @type boolean
565  * @return true if response APDU has data field
566  */
567 APDU.prototype.hasRData = function() {
568 	return ((typeof(this.rdata) != "undefined") && (this.rdata != null));
569 }
570 
571 
572 
573 /**
574  * Sets the status word for the response ADPU
575  *
576  * @param {Number} sw the status word
577  */
578 APDU.prototype.setSW = function(sw) {
579 	this.SW = sw;
580 }
581 
582 
583 
584 /**
585  * Get the status word
586  *
587  * @type Number
588  * @return the status word
589  */
590 APDU.prototype.getSW = function() {
591 	return this.SW;
592 }
593 
594 
595 
596 /**
597  * Return a human readable form of this object
598  */
599 APDU.prototype.toString = function() {
600 	return this.getCommandAPDU().toString(HEX) + " : " + this.getResponseAPDU().toString(HEX);
601 }
602 
603 
604 
605 /**
606  * Simple unit test
607  */
608 APDU.test = function() {
609 	// Case 1
610 	var a = new APDU(0x00, 0xA4, 0x00, 0x0C);
611 	print(a);
612 	var b = a.getCommandAPDU();
613 	assert(b.toString(HEX) == "00A4000C");
614 	var c = new APDU(b);
615 	assert(a.toString() == c.toString());
616 
617 	// Case 2 Short
618 	var a = new APDU(0x00, 0xA4, 0x00, 0x0C, 0);
619 	print(a);
620 	var b = a.getCommandAPDU();
621 	assert(b.toString(HEX) == "00A4000C00");
622 	var c = new APDU(b);
623 	assert(a.toString() == c.toString());
624 
625 	// Case 2 Extended
626 	var a = new APDU(0x00, 0xA4, 0x00, 0x0C, 65536);
627 	print(a);
628 	var b = a.getCommandAPDU();
629 	assert(b.toString(HEX) == "00A4000C000000");
630 	var c = new APDU(b);
631 	print(c);
632 	assert(a.toString() == c.toString());
633 
634 	// Case 3 Short
635 	var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString("3F00", HEX));
636 	print(a);
637 	var b = a.getCommandAPDU();
638 	assert(b.toString(HEX) == "00A4000C023F00");
639 	var c = new APDU(b);
640 	assert(a.toString() == c.toString());
641 
642 	// Case 3 Extended
643 	var data = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
644 	var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString(data, HEX));
645 	print(a);
646 	var b = a.getCommandAPDU();
647 	assert(b.toString(HEX) == "00A4000C000100" + data);
648 	var c = new APDU(b);
649 	assert(a.toString() == c.toString());
650 
651 	// Case 4 Short
652 	var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString("3F00", HEX), 0);
653 	print(a);
654 	var b = a.getCommandAPDU();
655 	assert(b.toString(HEX) == "00A4000C023F0000");
656 	var c = new APDU(b);
657 	assert(a.toString() == c.toString());
658 	
659 	// Case 4b Extended
660 	var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString(data, HEX), 0);
661 	print(a);
662 	var b = a.getCommandAPDU();
663 	assert(b.toString(HEX) == "00A4000C000100" + data + "0000");
664 	var c = new APDU(b);
665 	assert(a.toString() == c.toString());
666 
667 	// Case 4b Extended
668 	var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString("3F00", HEX), 65536);
669 	print(a);
670 	var b = a.getCommandAPDU();
671 	assert(b.toString(HEX) == "00A4000C0000023F000000");
672 	var c = new APDU(b);
673 	assert(a.toString() == c.toString());
674 	
675 }
676 
677 
678 
679 /**
680  * Create an adapter to decode a APDU for data unit handling
681  *
682  * @class Adapter class to decode APDUs for data unit handling
683  * @constructor
684  * @param {APDU} apdu the APDU to decode
685  */ 
686 function DataUnitAPDU(apdu) {
687 	this.apdu = apdu;
688 
689 	var p1 = apdu.getP1();
690 	
691 	if ((this.apdu.getINS() & 1) == 0) {		// Even instruction
692 		if ((p1 & 0x80) == 0x80) {				// SFI in P1
693 			this.offset = this.apdu.getP2();
694 			this.sfi = p1 & 0x1F;
695 		} else {
696 			this.offset = (p1 << 8) + this.apdu.getP2();
697 		}
698 		this.data = apdu.getCData();
699 	} else {									// Odd instruction
700 		var p2 = apdu.getP2();
701 		var fid = (p1 << 8) + p2;				// FID in P1 P2
702 		// If bits b16 - b6 are all 0 and b5 - b1 are not all equal, then we have an SFI 
703 		if (((fid & 0xFFE0) == 0) && ((fid & 0x1F) >= 1) && ((fid & 0x1F) <= 30)) {
704 			this.sfi = fid & 0x1F;
705 		} else if (fid != 0) {					// FID = 0000 means current file
706 			var bb = new ByteBuffer();
707 			bb.append(p1);
708 			bb.append(p2);
709 			this.fid = bb.toByteString();
710 		}
711 
712 		var a = this.apdu.getCDataAsTLVList();
713 
714 		if ((a.length < 1) || (a.length > 2)) {
715 			throw new GPError("DataUnitAPDU", GPError.INVALID_DATA, APDU.SW_INVDATA, "Invalid data for odd instruction data handling command, less than one or more than two elements in TLV");
716 		}
717 
718 		var o = a.index(0);
719 		if (o.getTag() != 0x54) {
720 			throw new GPError("DataUnitAPDU", GPError.INVALID_DATA, APDU.SW_INVDATA, "Invalid data for odd instruction data handling command, first tag must be '54' offset");
721 		}
722 		
723 		this.offset = o.getValue().toUnsigned();
724 		
725 		if (a.length == 2) {
726 			var o = a.index(1);
727 			var t = o.getTag();
728 			if ((t != 0x53) && (t != 0x73)) {
729 				throw new GPError("DataUnitAPDU", GPError.INVALID_DATA, APDU.SW_INVDATA, "Invalid data for odd instruction data handling command, second tag must be '53' or '73'");
730 			}
731 		
732 			this.data = o.getValue();
733 		}
734 	}
735 }
736 
737 
738 
739 /**
740  * Gets the short file identifier, if one defined
741  * 
742  * @type Number
743  * @return the short file identifier in the range 1 to 30 or -1 if not defined
744  */
745 DataUnitAPDU.prototype.getSFI = function() {
746 	if (typeof(this.sfi) == "undefined") {
747 		return -1;
748 	}
749 	return this.sfi;
750 }
751 
752 
753 
754 /**
755  * Gets the file identifier, if one defined
756  * 
757  * @type ByteString
758  * @return the file identifier or null if not defined
759  */
760 DataUnitAPDU.prototype.getFID = function() {
761 	if (typeof(this.fid) == "undefined") {
762 		return null;
763 	}
764 	return this.fid;
765 }
766 
767 
768 
769 /**
770  * Gets the offset
771  * 
772  * @type Number
773  * @return the offset to read from or write to
774  */
775 DataUnitAPDU.prototype.getOffset = function() {
776 	return this.offset;
777 }
778 
779 
780 
781 /**
782  * Get the command data
783  *
784  * @type ByteString
785  * @return the command data
786  */
787 DataUnitAPDU.prototype.getCData = function() {
788 	if (!this.hasCData()) {
789 		throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_INVDATA, "No data in command APDU");
790 	}
791 	return this.data;
792 }
793 
794 
795 
796 /**
797  * Returns true if command data in contained in the APDU
798  *
799  * @type boolean
800  * @returns true if command data contained
801  */
802 DataUnitAPDU.prototype.hasCData = function() {
803 	return ((typeof(this.data) != "undefined") && (this.data != null));
804 }
805 
806 
807 
808 /**
809  * Simple Unit Test
810  */
811 DataUnitAPDU.test = function() {
812 	var apdu = new APDU(0x00, 0xB0, 0, 0, 0);
813 	var dh = new DataUnitAPDU(apdu);
814 	assert(dh.getOffset() == 0);
815 	assert(!dh.hasCData());
816 	
817 	var apdu = new APDU(0x00, 0xB0, 0x7F, 0xFF, 0);
818 	var dh = new DataUnitAPDU(apdu);
819 	assert(dh.getOffset() == 0x7FFF);
820 	assert(!dh.hasCData());
821 
822 	var apdu = new APDU(0x00, 0xB0, 0x80, 0, 0);
823 	var dh = new DataUnitAPDU(apdu);
824 	assert(dh.getOffset() == 0);
825 	assert(!dh.hasCData());
826 
827 	var apdu = new APDU(0x00, 0xB0, 0x80, 0xFF, 0);
828 	var dh = new DataUnitAPDU(apdu);
829 	assert(dh.getOffset() == 0xFF);
830 	assert(!dh.hasCData());
831 
832 	var apdu = new APDU(0x00, 0xB1, 0, 0, new ByteString("540100", HEX), 0);
833 	var dh = new DataUnitAPDU(apdu);
834 	assert(dh.getOffset() == 0);
835 	assert(!dh.hasCData());
836 
837 	var apdu = new APDU(0x00, 0xB1, 0, 0, new ByteString("5401FF", HEX), 0);
838 	var dh = new DataUnitAPDU(apdu);
839 	assert(dh.getOffset() == 0xFF);
840 	assert(!dh.hasCData());
841 
842 	var apdu = new APDU(0x00, 0xB1, 0, 0, new ByteString("540401000000", HEX), 0);
843 	var dh = new DataUnitAPDU(apdu);
844 	assert(dh.getOffset() == 0x01000000);
845 	assert(!dh.hasCData());
846 
847 	var data = new ByteString("1234", ASCII);
848 	
849 	var apdu = new APDU(0x00, 0xD6, 0, 0, data);
850 	var dh = new DataUnitAPDU(apdu);
851 	assert(dh.getOffset() == 0);
852 	assert(dh.hasCData());
853 	assert(dh.getCData().equals(data));
854 	
855 	var apdu = new APDU(0x00, 0xD6, 0x7F, 0xFF, data);
856 	var dh = new DataUnitAPDU(apdu);
857 	assert(dh.getOffset() == 0x7FFF);
858 	assert(dh.hasCData());
859 	assert(dh.getCData().equals(data));
860 
861 	var apdu = new APDU(0x00, 0xD6, 0x80, 0, data);
862 	var dh = new DataUnitAPDU(apdu);
863 	assert(dh.getOffset() == 0);
864 	assert(dh.hasCData());
865 	assert(dh.getCData().equals(data));
866 
867 	var apdu = new APDU(0x00, 0xD6, 0x80, 0xFF, data);
868 	var dh = new DataUnitAPDU(apdu);
869 	assert(dh.getOffset() == 0xFF);
870 	assert(dh.hasCData());
871 	assert(dh.getCData().equals(data));
872 
873 	var apdu = new APDU(0x00, 0xD7, 0, 0, (new ByteString("5401005304", HEX)).concat(data), 0);
874 	var dh = new DataUnitAPDU(apdu);
875 	assert(dh.getOffset() == 0);
876 	assert(dh.hasCData());
877 	assert(dh.getCData().equals(data));
878 
879 	var apdu = new APDU(0x00, 0xD7, 0, 0, (new ByteString("5401FF5304", HEX)).concat(data), 0);
880 	var dh = new DataUnitAPDU(apdu);
881 	assert(dh.getOffset() == 0xFF);
882 	assert(dh.hasCData());
883 	assert(dh.getCData().equals(data));
884 
885 	var apdu = new APDU(0x00, 0xD7, 0, 0, (new ByteString("5404010000005304", HEX)).concat(data), 0);
886 	var dh = new DataUnitAPDU(apdu);
887 	assert(dh.getOffset() == 0x01000000);
888 	assert(dh.hasCData());
889 	assert(dh.getCData().equals(data));
890 }
891