1 /**
  2  *  ---------
  3  * |.##> <##.|  Open Smart Card Development Platform (www.openscdp.org)
  4  * |#       #|
  5  * |#       #|  Copyright (c) 1999-2018 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 exports.APDU = APDU;
 93 
 94 
 95 
 96 APDU.INS_DEACTIVATE		= 0x04;
 97 APDU.INS_VERIFY			= 0x20;
 98 APDU.INS_MANAGE_SE		= 0x22;
 99 APDU.INS_CHANGE_REFERENCE_DATA	= 0x24;
100 APDU.INS_PSO			= 0x2A;
101 APDU.INS_RESET_RETRY_COUNTER	= 0x2C;
102 APDU.INS_ACTIVATE		= 0x44;
103 APDU.INS_GENERATE_KEY_PAIR	= 0x46;
104 APDU.INS_GENERATE_KEY		= 0x48;
105 APDU.INS_INITIALIZE_UPDATE	= 0x50;
106 APDU.INS_EXTERNAL_AUTHENTICATE	= 0x82;
107 APDU.INS_GET_CHALLENGE		= 0x84;
108 APDU.INS_GENERAL_AUTHENTICATE	= 0x86;
109 APDU.INS_COMPUTE_DIGITAL_SIGN	= 0x9E;
110 APDU.INS_SELECT			= 0xA4;
111 APDU.INS_READBINARY		= 0xB0;
112 APDU.INS_READ_BINARY		= 0xB0;
113 APDU.INS_READ_RECORD		= 0xB2;
114 APDU.INS_VERIFY_CERTIFICATE	= 0xBE;
115 APDU.INS_GET_RESPONSE		= 0xC0;
116 APDU.INS_GET_DATA		= 0xCA;
117 APDU.INS_UPDATE_BINARY		= 0xD6;
118 APDU.INS_PUT_KEY		= 0xD8;
119 APDU.INS_PUT_DATA		= 0xDA;
120 APDU.INS_DELETE			= 0xE4;
121 APDU.INS_TERMINATE		= 0xE6;
122 APDU.INS_SET_STATUS		= 0xF0;
123 
124 APDU.SW_OK                 = 0x9000;      	/* Process completed                 */
125 
126 APDU.SW_TIMEOUT            = 0x6401;      	/* Exec error: Command timeout       */
127 
128 APDU.SW_OKMOREDATA         = 0x6100;      	/*-Process completed, more data available*/
129 APDU.SW_WARNING            = 0x6200;      	/*-Warning: NV-Ram not changed       */
130 APDU.SW_WARNING1           = 0x6201;      	/*-Warning: NV-Ram not changed 1     */
131 APDU.SW_DATAINV            = 0x6281;      	/*-Warning: Part of data corrupted   */
132 APDU.SW_EOF                = 0x6282;      	/*-Warning: End of file reached      */
133 APDU.SW_INVFILE            = 0x6283;      	/* Warning: Invalidated file         */
134 APDU.SW_INVFORMAT          = 0x6284;      	/* Warning: Invalid file control     */
135 APDU.SW_WARNINGNVCHG       = 0x6300;      	/*-Warning: NV-Ram changed           */
136 APDU.SW_WARNINGCOUNT       = 0x63C0;      	/*-Warning: Warning with counter     */
137 APDU.SW_WARNING0LEFT       = 0x63C0;      	/*-Warning: Verify fail, no try left */
138 APDU.SW_WARNING1LEFT       = 0x63C1;      	/*-Warning: Verify fail, 1 try left  */
139 APDU.SW_WARNING2LEFT       = 0x63C2;      	/*-Warning: Verify fail, 2 tries left*/
140 APDU.SW_WARNING3LEFT       = 0x63C3;      	/*-Warning: Verify fail, 3 tries left*/
141 APDU.SW_EXECERR            = 0x6400;      	/*-Exec error: NV-Ram not changed    */
142 APDU.SW_MEMERR             = 0x6501;      	/*-Exec error: Memory failure        */
143 APDU.SW_MEMERRWRITE        = 0x6581;      	/*-Exec error: Memory failure        */
144 APDU.SW_WRONGLENGTH        = 0x6700;      	/*-Checking error: Wrong length      */
145 
146 APDU.SW_CLANOTSUPPORTED    = 0x6800;      	/*-Checking error: Function in CLA byte not supported */
147 APDU.SW_LCNOTSUPPORTED     = 0x6881;      	/*-Checking error: Logical channel not supported */
148 APDU.SW_SMNOTSUPPORTED     = 0x6882;      	/*-Checking error: Secure Messaging not supported */
149 APDU.SW_LASTCMDEXPECTED    = 0x6883;      	/*-Checking error: Last command of the chain expected */
150 APDU.SW_CHAINNOTSUPPORTED  = 0x6884;      	/*-Checking error: Command chaining not supported */
151 
152 APDU.SW_COMNOTALLOWED      = 0x6900;      	/*-Checking error: Command not allowed */
153 APDU.SW_COMINCOMPATIBLE    = 0x6981;      	/*-Checking error: Command incompatible with file structure */
154 APDU.SW_SECSTATNOTSAT      = 0x6982;      	/*-Checking error: Security condition not satisfied */
155 APDU.SW_AUTHMETHLOCKED     = 0x6983;      	/*-Checking error: Authentication method locked */
156 APDU.SW_REFDATANOTUSABLE   = 0x6984;      	/*-Checking error: Reference data not usable */
157 APDU.SW_CONDOFUSENOTSAT    = 0x6985;      	/*-Checking error: Condition of use not satisfied */
158 APDU.SW_COMNOTALLOWNOEF    = 0x6986;      	/*-Checking error: Command not allowed (no current EF) */
159 APDU.SW_SMOBJMISSING       = 0x6987;      	/*-Checking error: Expected secure messaging object missing */
160 APDU.SW_INCSMDATAOBJECT    = 0x6988;      	/*-Checking error: Incorrect secure messaging data object */
161 
162 APDU.SW_INVPARA            = 0x6A00;      	/*-Checking error: Wrong parameter P1-P2 */
163 APDU.SW_INVDATA            = 0x6A80;      	/*-Checking error: Incorrect parameter in the command data field*/
164 APDU.SW_FUNCNOTSUPPORTED   = 0x6A81;      	/*-Checking error: Function not supported */
165 APDU.SW_NOAPPL             = 0x6A82;      	/*-Checking error: File not found    */
166 APDU.SW_FILENOTFOUND       = 0x6A82;      	/*-Checking error: File not found    */
167 APDU.SW_RECORDNOTFOUND     = 0x6A83;      	/*-Checking error: Record not found    */
168 APDU.SW_OUTOFMEMORY        = 0x6A84;      	/*-Checking error: Not enough memory space in the file   */
169 APDU.SW_INVLCTLV           = 0x6A85;      	/*-Checking error: Nc inconsistent with TLV structure */
170 APDU.SW_INVACC             = 0x6A85;      	/*-Checking error: Access cond. n/f  */
171 APDU.SW_INCP1P2            = 0x6A86;      	/*-Checking error: Incorrect P1-P2   */
172 APDU.SW_INVLC              = 0x6A87;      	/*-Checking error: Lc inconsistent with P1-P2 */
173 APDU.SW_RDNOTFOUND         = 0x6A88;      	/*-Checking error: Reference data not found*/
174 APDU.SW_FILEEXISTS         = 0x6A89;      	/*-Checking error: File already exists */
175 APDU.SW_DFNAMEEXISTS       = 0x6A8A;      	/*-Checking error: DF name already exists */
176 
177 APDU.SW_INVP1P2            = 0x6B00;      	/*-Checking error: Wrong parameter P1-P2 */
178 APDU.SW_INVLE              = 0x6C00;      	/*-Checking error: Invalid Le        */
179 APDU.SW_INVINS             = 0x6D00;      	/*-Checking error: Wrong instruction */
180 APDU.SW_INVCLA             = 0x6E00;      	/*-Checking error: Class not supported */
181 APDU.SW_ACNOTSATISFIED     = 0x9804;      	/* Access conditions not satisfied   */
182 APDU.SW_NOMORESTORAGE      = 0x9210;      	/* No more storage available         */
183 APDU.SW_GENERALERROR       = 0x6F00;      	/*-Checking error: No precise diagnosis */
184 
185 
186 /**
187  * Create an APDU object from the encoded form (Called internally)
188  *
189  * @param {ByteString} bs
190  */
191 APDU.prototype.fromByteString = function(bs) {
192 	if (bs.length < 4) {
193 		throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_GENERALERROR, "Command APDU must be at least 4 bytes long");
194 	}
195 	this.cla = bs.byteAt(0);
196 	this.ins = bs.byteAt(1);
197 	this.p1 = bs.byteAt(2);
198 	this.p2 = bs.byteAt(3);
199 
200 	if (bs.length > 4) {
201 		var extended = false;
202 
203 		var i = 4;
204 		var l = bs.length - i;
205 		var n = bs.byteAt(i++);
206 		l--;
207 
208 		if ((n == 0) && (l > 0)) {
209 			extended = true;
210 			if (l < 2) {
211 				throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Extended length APDU too short");
212 			}
213 			n = (bs.byteAt(i) << 8) + bs.byteAt(i + 1);
214 			i += 2;
215 			l -= 2;
216 		}
217 
218 		if (l > 0) {	// Case 3s / Case 3e / Case 4s / Case 4e
219 			if (l < n) {
220 				throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Invalid Lc in APDU");
221 			}
222 			this.cdata = bs.bytes(i, n);
223 			i += n;
224 			l -= n;
225 
226 			if (l > 0) {	// Case 4s / Case 4e
227 				n = bs.byteAt(i++);
228 				l--;
229 				if (extended) {
230 					if (l < 1) {
231 						throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Invalid Le in extended APDU");
232 					}
233 					n = (n << 8) + bs.byteAt(i++);
234 					l--;
235 				}
236 				this.ne = (extended && (n == 0) ? 65536 : n);
237 			}
238 		} else {
239 			this.ne = (extended && (n == 0) ? 65536 : n);
240 		}
241 
242 		if (l > 0) {
243 			throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Too many bytes in APDU");
244 		}
245 	}
246 }
247 
248 
249 
250 /**
251  * Get encoded command APDU
252  *
253  * @type ByteString
254  * @return the encoded command APDU
255  */
256 APDU.prototype.getCommandAPDU = function() {
257 	var bb = new ByteBuffer();
258 
259 	bb.append(this.cla);
260 	bb.append(this.ins);
261 	bb.append(this.p1);
262 	bb.append(this.p2);
263 
264 	var hasCData = (typeof(this.cdata) != "undefined");
265 	var hasNe = (typeof(this.ne) != "undefined");
266 
267 	var extended = ((hasCData && this.cdata.length > 255) ||
268 					(hasNe && this.ne > 256));
269 
270 	if (extended) {
271 		bb.append(0);
272 	}
273 
274 	if (hasCData && this.cdata.length > 0) {
275 		if (extended) {
276 			bb.append(this.cdata.length >> 8);
277 		}
278 		bb.append(this.cdata.length & 0xFF);
279 		bb.append(this.cdata);
280 	}
281 
282 	if (hasNe) {
283 		if (extended) {
284 			bb.append(this.ne >> 8);
285 		}
286 		bb.append(this.ne & 0xFF);
287 	}
288 
289 	return bb.toByteString();
290 }
291 
292 
293 
294 /**
295  * Get encoded response APDU
296  *
297  * @type ByteString
298  * @return the encoded response APDU
299  */
300 APDU.prototype.getResponseAPDU = function() {
301 	var bb = new ByteBuffer();
302 
303 	if (this.rdata) {
304 		bb.append(this.rdata);
305 	}
306 
307 	bb.append(this.SW >> 8);
308 	bb.append(this.SW & 0xFF);
309 
310 	return bb.toByteString();
311 }
312 
313 
314 
315 /**
316  * Gets the class byte
317  *
318  * @type Number
319  * @return the class byte
320  */
321 APDU.prototype.getCLA = function() {
322 	return this.cla;
323 }
324 
325 
326 
327 /**
328  * Sets the class byte, e.g. after a transformation
329  *
330  * @parameter {Number} the new CLA byte
331  */
332 APDU.prototype.setCLA = function(cla) {
333 	this.cla = cla;
334 }
335 
336 
337 
338 /**
339  * Test if command is an ISO command
340  *
341  * @type boolean
342  * @return true if command has ISO class byte
343  */
344 APDU.prototype.isISO = function() {
345 	return (this.cla & 0x80) == 0x00;
346 }
347 
348 
349 
350 /**
351  * Test if command uses extended length
352  *
353  * @type boolean
354  * @return true if APDU uses extended length
355  */
356 APDU.prototype.isExtendedLength = function() {
357 	return ((this.hasCData() && this.cdata.length > 255) || (this.hasLe() && (this.ne > 256)));
358 }
359 
360 
361 
362 /**
363  * Test if command chaining is indicated
364  *
365  * @type boolean
366  * @return true if chaining bit is set
367  */
368 APDU.prototype.isChained = function() {
369 	return (this.cla & 0x10) == 0x10;
370 }
371 
372 
373 
374 /**
375  * Test if command is send using secure messaging
376  *
377  * @type boolean
378  * @return true if secure messaging is indicated in CLA byte
379  */
380 APDU.prototype.isSecureMessaging = function() {
381 	return (this.cla & 0x08) == 0x08;
382 }
383 
384 
385 
386 /**
387  * Test if command is send using secure messaging
388  *
389  * @type boolean
390  * @return true if secure messaging is using an authenticated header
391  */
392 APDU.prototype.isAuthenticatedHeader = function() {
393 	return (this.cla & 0x0C) == 0x0C;
394 }
395 
396 
397 
398 /**
399  * Gets the instruction byte
400  *
401  * @type Number
402  * @return the instruction byte
403  */
404 APDU.prototype.getINS = function() {
405 	return this.ins;
406 }
407 
408 
409 
410 /**
411  * Gets the P1 byte
412  *
413  * @type Number
414  * @return the P1 byte
415  */
416 APDU.prototype.getP1 = function() {
417 	return this.p1;
418 }
419 
420 
421 
422 /**
423  * Gets the P2 byte
424  *
425  * @type Number
426  * @return the P2 byte
427  */
428 APDU.prototype.getP2 = function() {
429 	return this.p2;
430 }
431 
432 
433 
434 /**
435  * Set the command data
436  *
437  * @param {ByteString} cdata the command data
438  */
439 APDU.prototype.setCData = function(cdata) {
440 	if (cdata instanceof ByteString) {
441 		this.cdata = cdata;
442 	} else {
443 		delete this.cdata;
444 	}
445 }
446 
447 
448 
449 /**
450  * Gets the command data
451  *
452  * @type ByteString
453  * @return the command data, if any else undefined
454  */
455 APDU.prototype.getCData = function() {
456 	return this.cdata;
457 }
458 
459 
460 
461 /**
462  * Check if APDU has command data
463  *
464  * @type boolean
465  * @return true if command APDU has data field
466  */
467 APDU.prototype.hasCData = function() {
468 	return typeof(this.cdata) != "undefined";
469 }
470 
471 
472 
473 /**
474  * Gets the command data as a list of TLV objects
475  *
476  * @type TLVList
477  * @return the command data as TLV list, if any else undefined
478  */
479 APDU.prototype.getCDataAsTLVList = function() {
480 	if (!this.hasCData()) {
481 		throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_INVDATA, "No data in command APDU");
482 	}
483 
484 	try	{
485 		var a = new TLVList(this.cdata, TLV.EMV);
486 	}
487 	catch(e) {
488 		throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_INVDATA, "Invalid TLV data in command APDU");
489 	}
490 
491 	return a;
492 }
493 
494 
495 
496 /**
497  * Gets the number of expected bytes
498  *
499  * @type Number
500  * @return the number of expected bytes or undefined
501  */
502 APDU.prototype.getNe = function() {
503 	return this.ne;
504 }
505 
506 
507 
508 /**
509  * Check if APDU has Le field
510  *
511  * @type boolean
512  * @return true if command APDU has Le field
513  */
514 APDU.prototype.hasLe = function() {
515 	return typeof(this.ne) != "undefined";
516 }
517 
518 
519 
520 /**
521  * Set secure channel object to be used in wrap and unwrap methods
522  *
523  * @param {SecureChannel} secureChannel the channel
524  */
525 APDU.prototype.setSecureChannel = function(secureChannel) {
526 	this.secureChannel = secureChannel;
527 }
528 
529 
530 
531 /**
532  * Return the secure channel, if any
533  *
534  * @type SecureChannel
535  * @return the secure channel
536  */
537 APDU.prototype.getSecureChannel = function() {
538 	return this.secureChannel;
539 }
540 
541 
542 
543 /**
544  * Test if a secure channel is defined for this APDU
545  *
546  * @type boolean
547  * @return true, if secure channel is set
548  */
549 APDU.prototype.hasSecureChannel = function() {
550 	return (typeof(this.secureChannel) != "undefined") && (this.secureChannel != null);
551 }
552 
553 
554 
555 /**
556  * Wrap APDU using secure channel
557  */
558 APDU.prototype.wrap = function() {
559 	if (this.hasSecureChannel()) {
560 		this.secureChannel.wrap(this);
561 	}
562 }
563 
564 
565 
566 /**
567  * Unwrap APDU using secure channel
568  */
569 APDU.prototype.unwrap = function() {
570 	if (this.hasSecureChannel()) {
571 		this.secureChannel.unwrap(this);
572 	}
573 }
574 
575 
576 
577 /**
578  * Sets the response data field for the response APDU
579  *
580  * @param {ByteString} data the response data field
581  */
582 APDU.prototype.setRData = function(data) {
583 	if ((data.length > 256) && !this.isExtendedLength()) {
584 		throw new GPError("APDU", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Too many bytes in APDU");
585 	}
586 	this.rdata = data;
587 }
588 
589 
590 
591 /**
592  * Get the response data
593  *
594  * @type ByteString
595  * @return the response data
596  */
597 APDU.prototype.getRData = function() {
598 	return this.rdata;
599 }
600 
601 
602 
603 /**
604  * Check if APDU has response data
605  *
606  * @type boolean
607  * @return true if response APDU has data field
608  */
609 APDU.prototype.hasRData = function() {
610 	return ((typeof(this.rdata) != "undefined") && (this.rdata != null));
611 }
612 
613 
614 
615 /**
616  * Sets the status word for the response ADPU
617  *
618  * @param {Number} sw the status word
619  */
620 APDU.prototype.setSW = function(sw) {
621 	this.SW = sw;
622 }
623 
624 
625 
626 /**
627  * Get the status word
628  *
629  * @type Number
630  * @return the status word
631  */
632 APDU.prototype.getSW = function() {
633 	return this.SW;
634 }
635 
636 
637 
638 /**
639  * Return a human readable form of this object
640  */
641 APDU.prototype.toString = function() {
642 	return this.getCommandAPDU().toString(HEX) + " : " + this.getResponseAPDU().toString(HEX);
643 }
644 
645 
646 
647 /**
648  * Simple unit test
649  */
650 APDU.test = function() {
651 	// Case 1
652 	var a = new APDU(0x00, 0xA4, 0x00, 0x0C);
653 	print(a);
654 	assert(!a.isExtendedLength());
655 	var b = a.getCommandAPDU();
656 	assert(b.toString(HEX) == "00A4000C");
657 	var c = new APDU(b);
658 	assert(a.toString() == c.toString());
659 
660 	// Case 2 Short
661 	var a = new APDU(0x00, 0xA4, 0x00, 0x0C, 0);
662 	print(a);
663 	assert(!a.isExtendedLength());
664 	var b = a.getCommandAPDU();
665 	assert(b.toString(HEX) == "00A4000C00");
666 	var c = new APDU(b);
667 	assert(a.toString() == c.toString());
668 
669 	// Case 2 Extended
670 	var a = new APDU(0x00, 0xA4, 0x00, 0x0C, 65536);
671 	print(a);
672 	assert(a.isExtendedLength());
673 	var b = a.getCommandAPDU();
674 	assert(b.toString(HEX) == "00A4000C000000");
675 	var c = new APDU(b);
676 	print(c);
677 	assert(a.toString() == c.toString());
678 
679 	// Case 3 Short
680 	var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString("3F00", HEX));
681 	print(a);
682 	assert(!a.isExtendedLength());
683 	var b = a.getCommandAPDU();
684 	assert(b.toString(HEX) == "00A4000C023F00");
685 	var c = new APDU(b);
686 	assert(a.toString() == c.toString());
687 
688 	// Case 3 Extended
689 	var data = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF";
690 	var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString(data, HEX));
691 	print(a);
692 	assert(a.isExtendedLength());
693 	var b = a.getCommandAPDU();
694 	assert(b.toString(HEX) == "00A4000C000100" + data);
695 	var c = new APDU(b);
696 	assert(a.toString() == c.toString());
697 
698 	// Case 4 Short
699 	var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString("3F00", HEX), 0);
700 	print(a);
701 	assert(!a.isExtendedLength());
702 	var b = a.getCommandAPDU();
703 	assert(b.toString(HEX) == "00A4000C023F0000");
704 	var c = new APDU(b);
705 	assert(a.toString() == c.toString());
706 
707 	// Case 4b Extended
708 	var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString(data, HEX), 0);
709 	print(a);
710 	assert(a.isExtendedLength());
711 	var b = a.getCommandAPDU();
712 	assert(b.toString(HEX) == "00A4000C000100" + data + "0000");
713 	var c = new APDU(b);
714 	assert(a.toString() == c.toString());
715 
716 	// Case 4b Extended
717 	var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString("3F00", HEX), 65536);
718 	print(a);
719 	assert(a.isExtendedLength());
720 	var b = a.getCommandAPDU();
721 	assert(b.toString(HEX) == "00A4000C0000023F000000");
722 	var c = new APDU(b);
723 	assert(a.toString() == c.toString());
724 }
725