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