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