      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