OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 /** |
| 6 * @fileoverview Low level usb cruft to talk gnubby. |
| 7 * @author mschilder@google.com |
| 8 */ |
| 9 |
| 10 'use strict'; |
| 11 |
| 12 // Global Gnubby instance counter. |
| 13 var gnubby_id = 0; |
| 14 |
| 15 /** |
| 16 * Creates a worker Gnubby instance. |
| 17 * @constructor |
| 18 * @param {number=} opt_busySeconds to retry an exchange upon a BUSY result. |
| 19 */ |
| 20 function usbGnubby(opt_busySeconds) { |
| 21 this.dev = null; |
| 22 this.cid = (++gnubby_id) & 0x00ffffff; // Pick unique channel. |
| 23 this.rxframes = []; |
| 24 this.synccnt = 0; |
| 25 this.rxcb = null; |
| 26 this.closed = false; |
| 27 this.commandPending = false; |
| 28 this.notifyOnClose = []; |
| 29 this.busyMillis = (opt_busySeconds ? opt_busySeconds * 1000 : 2500); |
| 30 } |
| 31 |
| 32 /** |
| 33 * Sets usbGnubby's Gnubbies singleton. |
| 34 * @param {Gnubbies} gnubbies |
| 35 */ |
| 36 usbGnubby.setGnubbies = function(gnubbies) { |
| 37 /** @private {Gnubbies} */ |
| 38 usbGnubby.gnubbies_ = gnubbies; |
| 39 }; |
| 40 |
| 41 /** |
| 42 * @param {function(number, Array.<llGnubbyDeviceId>)} cb Called back with the |
| 43 * result of enumerating. |
| 44 */ |
| 45 usbGnubby.prototype.enumerate = function(cb) { |
| 46 if (!cb) cb = usbGnubby.defaultCallback; |
| 47 if (this.closed) { |
| 48 cb(-llGnubby.GONE); |
| 49 return; |
| 50 } |
| 51 if (!usbGnubby.gnubbies_) { |
| 52 cb(-llGnubby.NODEVICE); |
| 53 return; |
| 54 } |
| 55 |
| 56 usbGnubby.gnubbies_.enumerate(cb); |
| 57 }; |
| 58 |
| 59 /** |
| 60 * Opens the gnubby with the given index, or the first found gnubby if no |
| 61 * index is specified. |
| 62 * @param {llGnubbyDeviceId|undefined} opt_which The device to open. |
| 63 * @param {function(number)|undefined} opt_cb Called with result of opening the |
| 64 * gnubby. |
| 65 */ |
| 66 usbGnubby.prototype.open = function(opt_which, opt_cb) { |
| 67 var cb = opt_cb ? opt_cb : usbGnubby.defaultCallback; |
| 68 if (this.closed) { |
| 69 cb(-llGnubby.GONE); |
| 70 return; |
| 71 } |
| 72 this.closingWhenIdle = false; |
| 73 |
| 74 if (document.location.href.indexOf('_generated_') == -1) { |
| 75 // Not background page. |
| 76 // Pick more random cid to tell things apart on the usb bus. |
| 77 var rnd = UTIL_getRandom(2); |
| 78 this.cid ^= (rnd[0] << 16) | (rnd[1] << 8); |
| 79 } |
| 80 |
| 81 var self = this; |
| 82 function addSelfAsClient(which) { |
| 83 self.cid &= 0x00ffffff; |
| 84 self.cid |= ((which.device + 1) << 24); // For debugging. |
| 85 |
| 86 usbGnubby.gnubbies_.addClient(which, self, function(rc, device) { |
| 87 self.dev = device; |
| 88 cb(rc); |
| 89 }); |
| 90 } |
| 91 |
| 92 if (!usbGnubby.gnubbies_) { |
| 93 cb(-llGnubby.NODEVICE); |
| 94 return; |
| 95 } |
| 96 if (opt_which) { |
| 97 addSelfAsClient(opt_which); |
| 98 } else { |
| 99 usbGnubby.gnubbies_.enumerate(function(rc, devs) { |
| 100 if (rc || !devs.length) { |
| 101 cb(-llGnubby.NODEVICE); |
| 102 return; |
| 103 } |
| 104 addSelfAsClient(devs[0]); |
| 105 }); |
| 106 } |
| 107 }; |
| 108 |
| 109 /** |
| 110 * @return {boolean} Whether this gnubby has any command outstanding. |
| 111 * @private |
| 112 */ |
| 113 usbGnubby.prototype.inUse_ = function() { |
| 114 return this.commandPending; |
| 115 }; |
| 116 |
| 117 /** Closes this gnubby. */ |
| 118 usbGnubby.prototype.close = function() { |
| 119 this.closed = true; |
| 120 |
| 121 if (this.dev) { |
| 122 console.log(UTIL_fmt('usbGnubby.close()')); |
| 123 this.rxframes = []; |
| 124 this.rxcb = null; |
| 125 var dev = this.dev; |
| 126 this.dev = null; |
| 127 var self = this; |
| 128 // Wait a bit in case simpleton client tries open next gnubby. |
| 129 // Without delay, gnubbies would drop all idle devices, before client |
| 130 // gets to the next one. |
| 131 window.setTimeout( |
| 132 function() { |
| 133 usbGnubby.gnubbies_.removeClient(dev, self); |
| 134 }, 300); |
| 135 } |
| 136 }; |
| 137 |
| 138 /** |
| 139 * Asks this gnubby to close when it gets a chance. |
| 140 * @param {Function=} cb called back when closed. |
| 141 */ |
| 142 usbGnubby.prototype.closeWhenIdle = function(cb) { |
| 143 if (!this.inUse_()) { |
| 144 this.close(); |
| 145 if (cb) cb(); |
| 146 return; |
| 147 } |
| 148 this.closingWhenIdle = true; |
| 149 if (cb) this.notifyOnClose.push(cb); |
| 150 }; |
| 151 |
| 152 /** |
| 153 * Close and notify every caller that it is now closed. |
| 154 * @private |
| 155 */ |
| 156 usbGnubby.prototype.idleClose_ = function() { |
| 157 this.close(); |
| 158 while (this.notifyOnClose.length != 0) { |
| 159 var cb = this.notifyOnClose.shift(); |
| 160 cb(); |
| 161 } |
| 162 }; |
| 163 |
| 164 /** |
| 165 * Notify callback for every frame received. |
| 166 * @private |
| 167 */ |
| 168 usbGnubby.prototype.notifyFrame_ = function(cb) { |
| 169 if (this.rxframes.length != 0) { |
| 170 // Already have frames; continue. |
| 171 if (cb) window.setTimeout(cb, 0); |
| 172 } else { |
| 173 this.rxcb = cb; |
| 174 } |
| 175 }; |
| 176 |
| 177 /** |
| 178 * Called by low level driver with a frame. |
| 179 * @param {ArrayBuffer} frame |
| 180 * @return {boolean} Whether this client is still interested in receiving |
| 181 * frames from its device. |
| 182 */ |
| 183 usbGnubby.prototype.receivedFrame = function(frame) { |
| 184 if (this.closed) return false; // No longer interested. |
| 185 |
| 186 if (!this.checkCID_(frame)) { |
| 187 // Not for me, ignore. |
| 188 return true; |
| 189 } |
| 190 |
| 191 this.rxframes.push(frame); |
| 192 |
| 193 // Callback self in case we were waiting. Once. |
| 194 var cb = this.rxcb; |
| 195 this.rxcb = null; |
| 196 if (cb) window.setTimeout(cb, 0); |
| 197 |
| 198 return true; |
| 199 }; |
| 200 |
| 201 /** |
| 202 * @return {ArrayBuffer} oldest received frame. Throw if none. |
| 203 * @private |
| 204 */ |
| 205 usbGnubby.prototype.readFrame_ = function() { |
| 206 if (this.rxframes.length == 0) throw 'rxframes empty!'; |
| 207 |
| 208 var frame = this.rxframes.shift(); |
| 209 return frame; |
| 210 }; |
| 211 |
| 212 // Poll from rxframes[]. |
| 213 // timeout in seconds. |
| 214 usbGnubby.prototype.read_ = function(cmd, timeout, cb) { |
| 215 if (this.closed) { cb(-llGnubby.GONE); return; } |
| 216 if (!this.dev) { cb(-llGnubby.NODEVICE); return; } |
| 217 |
| 218 var tid = null; // timeout timer id. |
| 219 var callback = cb; |
| 220 var self = this; |
| 221 |
| 222 var msg = null; |
| 223 var seqno = 0; |
| 224 var count = 0; |
| 225 |
| 226 /** |
| 227 * Schedule call to cb if not called yet. |
| 228 * @param {number} a Return code. |
| 229 * @param {Object=} b Optional data. |
| 230 */ |
| 231 function schedule_cb(a, b) { |
| 232 self.commandPending = false; |
| 233 if (tid) { |
| 234 // Cancel timeout timer. |
| 235 window.clearTimeout(tid); |
| 236 tid = null; |
| 237 } |
| 238 var c = callback; |
| 239 if (c) { |
| 240 callback = null; |
| 241 window.setTimeout(function() { c(a, b); }, 0); |
| 242 } |
| 243 if (self.closingWhenIdle) self.idleClose_(); |
| 244 }; |
| 245 |
| 246 function read_timeout() { |
| 247 if (!callback || !tid) return; // Already done. |
| 248 |
| 249 console.error(UTIL_fmt( |
| 250 '[' + self.cid.toString(16) + '] timeout!')); |
| 251 |
| 252 if (self.dev) { |
| 253 self.dev.destroy(); // Stop pretending this thing works. |
| 254 } |
| 255 |
| 256 tid = null; |
| 257 |
| 258 schedule_cb(-llGnubby.TIMEOUT); |
| 259 }; |
| 260 |
| 261 function cont_frame() { |
| 262 if (!callback || !tid) return; // Already done. |
| 263 |
| 264 var f = new Uint8Array(self.readFrame_()); |
| 265 var rcmd = f[4]; |
| 266 var total_len = (f[5] << 8) + f[6]; |
| 267 |
| 268 if (rcmd == llGnubby.CMD_ERROR && total_len == 1) { |
| 269 // Error from device; forward. |
| 270 console.log(UTIL_fmt( |
| 271 '[' + self.cid.toString(16) + '] error frame ' + |
| 272 UTIL_BytesToHex(f))); |
| 273 if (f[7] == llGnubby.GONE) { |
| 274 self.closed = true; |
| 275 } |
| 276 schedule_cb(-f[7]); |
| 277 return; |
| 278 } |
| 279 |
| 280 if ((rcmd & 0x80)) { |
| 281 // Not an CONT frame, ignore. |
| 282 console.log(UTIL_fmt( |
| 283 '[' + self.cid.toString(16) + '] ignoring non-cont frame ' + |
| 284 UTIL_BytesToHex(f))); |
| 285 self.notifyFrame_(cont_frame); |
| 286 return; |
| 287 } |
| 288 |
| 289 var seq = (rcmd & 0x7f); |
| 290 if (seq != seqno++) { |
| 291 console.log(UTIL_fmt( |
| 292 '[' + self.cid.toString(16) + '] bad cont frame ' + |
| 293 UTIL_BytesToHex(f))); |
| 294 schedule_cb(-llGnubby.INVALID_SEQ); |
| 295 return; |
| 296 } |
| 297 |
| 298 // Copy payload. |
| 299 for (var i = 5; i < f.length && count < msg.length; ++i) { |
| 300 msg[count++] = f[i]; |
| 301 } |
| 302 |
| 303 if (count == msg.length) { |
| 304 // Done. |
| 305 schedule_cb(-llGnubby.OK, msg.buffer); |
| 306 } else { |
| 307 // Need more CONT frame(s). |
| 308 self.notifyFrame_(cont_frame); |
| 309 } |
| 310 } |
| 311 |
| 312 function init_frame() { |
| 313 if (!callback || !tid) return; // Already done. |
| 314 |
| 315 var f = new Uint8Array(self.readFrame_()); |
| 316 |
| 317 var rcmd = f[4]; |
| 318 var total_len = (f[5] << 8) + f[6]; |
| 319 |
| 320 if (rcmd == llGnubby.CMD_ERROR && total_len == 1) { |
| 321 // Error from device; forward. |
| 322 // Don't log busy frames, they're "normal". |
| 323 if (f[7] != llGnubby.BUSY) { |
| 324 console.log(UTIL_fmt( |
| 325 '[' + self.cid.toString(16) + '] error frame ' + |
| 326 UTIL_BytesToHex(f))); |
| 327 } |
| 328 if (f[7] == llGnubby.GONE) { |
| 329 self.closed = true; |
| 330 } |
| 331 schedule_cb(-f[7]); |
| 332 return; |
| 333 } |
| 334 |
| 335 if (!(rcmd & 0x80)) { |
| 336 // Not an init frame, ignore. |
| 337 console.log(UTIL_fmt( |
| 338 '[' + self.cid.toString(16) + '] ignoring non-init frame ' + |
| 339 UTIL_BytesToHex(f))); |
| 340 self.notifyFrame_(init_frame); |
| 341 return; |
| 342 } |
| 343 |
| 344 if (rcmd != cmd) { |
| 345 // Not expected ack, read more. |
| 346 console.log(UTIL_fmt( |
| 347 '[' + self.cid.toString(16) + '] ignoring non-ack frame ' + |
| 348 UTIL_BytesToHex(f))); |
| 349 self.notifyFrame_(init_frame); |
| 350 return; |
| 351 } |
| 352 |
| 353 // Copy payload. |
| 354 msg = new Uint8Array(total_len); |
| 355 for (var i = 7; i < f.length && count < msg.length; ++i) { |
| 356 msg[count++] = f[i]; |
| 357 } |
| 358 |
| 359 if (count == msg.length) { |
| 360 // Done. |
| 361 schedule_cb(-llGnubby.OK, msg.buffer); |
| 362 } else { |
| 363 // Need more CONT frame(s). |
| 364 self.notifyFrame_(cont_frame); |
| 365 } |
| 366 } |
| 367 |
| 368 // Start timeout timer. |
| 369 tid = window.setTimeout(read_timeout, 1000.0 * timeout); |
| 370 |
| 371 // Schedule read of first frame. |
| 372 self.notifyFrame_(init_frame); |
| 373 }; |
| 374 |
| 375 /** |
| 376 * @param {ArrayBuffer} frame |
| 377 * @return {boolean} Whether frame is for my channel. |
| 378 * @private |
| 379 */ |
| 380 usbGnubby.prototype.checkCID_ = function(frame) { |
| 381 var f = new Uint8Array(frame); |
| 382 var c = (f[0] << 24) | |
| 383 (f[1] << 16) | |
| 384 (f[2] << 8) | |
| 385 (f[3]); |
| 386 return c === this.cid || |
| 387 c === 0; // Generic notification. |
| 388 }; |
| 389 |
| 390 /** |
| 391 * Queue command for sending. |
| 392 * @param {number} cmd The command to send. |
| 393 * @param {ArrayBuffer} data |
| 394 * @private |
| 395 */ |
| 396 usbGnubby.prototype.write_ = function(cmd, data) { |
| 397 if (this.closed) return; |
| 398 if (!this.dev) return; |
| 399 |
| 400 this.commandPending = true; |
| 401 |
| 402 this.dev.queueCommand(this.cid, cmd, data); |
| 403 }; |
| 404 |
| 405 /** |
| 406 * Writes the command, and calls back when the command's reply is received. |
| 407 * @param {number} cmd The command to send. |
| 408 * @param {ArrayBuffer} data |
| 409 * @param {number} timeout Timeout in seconds. |
| 410 * @param {function(number, ArrayBuffer=)} cb |
| 411 * @private |
| 412 */ |
| 413 usbGnubby.prototype.exchange_ = function(cmd, data, timeout, cb) { |
| 414 var busyWait = new CountdownTimer(this.busyMillis); |
| 415 var self = this; |
| 416 |
| 417 function retryBusy(rc, rc_data) { |
| 418 if (rc == -llGnubby.BUSY && !busyWait.expired()) { |
| 419 if (usbGnubby.gnubbies_) { |
| 420 usbGnubby.gnubbies_.resetInactivityTimer(timeout * 1000); |
| 421 } |
| 422 self.write_(cmd, data); |
| 423 self.read_(cmd, timeout, retryBusy); |
| 424 } else { |
| 425 busyWait.clearTimeout(); |
| 426 cb(rc, rc_data); |
| 427 } |
| 428 } |
| 429 |
| 430 retryBusy(-llGnubby.BUSY, undefined); // Start work. |
| 431 }; |
| 432 |
| 433 // For console interaction. |
| 434 usbGnubby.defaultCallback = function(rc, data) { |
| 435 var msg = 'defaultCallback(' + rc; |
| 436 if (data) { |
| 437 if (typeof data == 'string') msg += ', ' + data; |
| 438 else msg += ', ' + UTIL_BytesToHex(new Uint8Array(data)); |
| 439 } |
| 440 msg += ')'; |
| 441 console.log(UTIL_fmt(msg)); |
| 442 }; |
| 443 |
| 444 // Send nonce to device, flush read queue until match. |
| 445 usbGnubby.prototype.sync = function(cb) { |
| 446 if (!cb) cb = usbGnubby.defaultCallback; |
| 447 if (this.closed) { |
| 448 cb(-llGnubby.GONE); |
| 449 return; |
| 450 } |
| 451 |
| 452 var done = false; |
| 453 var trycount = 6; |
| 454 var tid = null; |
| 455 var self = this; |
| 456 |
| 457 function callback(rc) { |
| 458 done = true; |
| 459 self.commandPending = false; |
| 460 if (tid) { |
| 461 window.clearTimeout(tid); |
| 462 tid = null; |
| 463 } |
| 464 if (rc) console.warn(UTIL_fmt('sync failed: ' + rc)); |
| 465 if (cb) cb(rc); |
| 466 if (self.closingWhenIdle) self.idleClose_(); |
| 467 } |
| 468 |
| 469 function sendSentinel() { |
| 470 var data = new Uint8Array(1); |
| 471 data[0] = ++self.synccnt; |
| 472 self.write_(llGnubby.CMD_SYNC, data.buffer); |
| 473 } |
| 474 |
| 475 function checkSentinel() { |
| 476 var f = new Uint8Array(self.readFrame_()); |
| 477 |
| 478 // Device disappeared on us. |
| 479 if (f[4] == llGnubby.CMD_ERROR && |
| 480 f[5] == 0 && f[6] == 1 && |
| 481 f[7] == llGnubby.GONE) { |
| 482 self.closed = true; |
| 483 callback(-llGnubby.GONE); |
| 484 return; |
| 485 } |
| 486 |
| 487 // Eat everything else but expected sync reply. |
| 488 if (f[4] != llGnubby.CMD_SYNC || |
| 489 (f.length > 7 && /* fw pre-0.2.1 bug: does not echo sentinel */ |
| 490 f[7] != self.synccnt)) { |
| 491 // Read more. |
| 492 self.notifyFrame_(checkSentinel); |
| 493 return; |
| 494 } |
| 495 |
| 496 // Done. |
| 497 callback(-llGnubby.OK); |
| 498 }; |
| 499 |
| 500 function timeoutLoop() { |
| 501 if (done) return; |
| 502 |
| 503 if (trycount == 0) { |
| 504 // Failed. |
| 505 callback(-llGnubby.TIMEOUT); |
| 506 return; |
| 507 } |
| 508 |
| 509 --trycount; // Try another one. |
| 510 sendSentinel(); |
| 511 self.notifyFrame_(checkSentinel); |
| 512 tid = window.setTimeout(timeoutLoop, 500); |
| 513 }; |
| 514 |
| 515 timeoutLoop(); |
| 516 }; |
| 517 |
| 518 // Communication timeout values in seconds. |
| 519 usbGnubby.SHORT_TIMEOUT = 1; |
| 520 usbGnubby.NORMAL_TIMEOUT = 3; |
| 521 // Max timeout usb firmware has for smartcard response is 30 seconds. |
| 522 // Make our application level tolerance a little longer. |
| 523 usbGnubby.MAX_TIMEOUT = 31; |
| 524 |
| 525 usbGnubby.prototype.blink = function(data, cb) { |
| 526 if (!cb) cb = usbGnubby.defaultCallback; |
| 527 if (typeof data == 'number') { |
| 528 var d = new Uint8Array([data]); |
| 529 data = d.buffer; |
| 530 } |
| 531 this.exchange_(llGnubby.CMD_PROMPT, data, usbGnubby.NORMAL_TIMEOUT, cb); |
| 532 }; |
| 533 |
| 534 usbGnubby.prototype.lock = function(data, cb) { |
| 535 if (!cb) cb = usbGnubby.defaultCallback; |
| 536 if (typeof data == 'number') { |
| 537 var d = new Uint8Array([data]); |
| 538 data = d.buffer; |
| 539 } |
| 540 this.exchange_(llGnubby.CMD_LOCK, data, usbGnubby.NORMAL_TIMEOUT, cb); |
| 541 }; |
| 542 |
| 543 usbGnubby.prototype.unlock = function(cb) { |
| 544 if (!cb) cb = usbGnubby.defaultCallback; |
| 545 var data = new Uint8Array([0]); |
| 546 this.exchange_(llGnubby.CMD_LOCK, data.buffer, |
| 547 usbGnubby.NORMAL_TIMEOUT, cb); |
| 548 }; |
| 549 |
| 550 usbGnubby.prototype.sysinfo = function(cb) { |
| 551 if (!cb) cb = usbGnubby.defaultCallback; |
| 552 this.exchange_(llGnubby.CMD_SYSINFO, new ArrayBuffer(0), |
| 553 usbGnubby.NORMAL_TIMEOUT, cb); |
| 554 }; |
| 555 |
| 556 usbGnubby.prototype.wink = function(cb) { |
| 557 if (!cb) cb = usbGnubby.defaultCallback; |
| 558 this.exchange_(llGnubby.CMD_WINK, new ArrayBuffer(0), |
| 559 usbGnubby.NORMAL_TIMEOUT, cb); |
| 560 }; |
| 561 |
| 562 usbGnubby.prototype.dfu = function(data, cb) { |
| 563 if (!cb) cb = usbGnubby.defaultCallback; |
| 564 this.exchange_(llGnubby.CMD_DFU, data, usbGnubby.NORMAL_TIMEOUT, cb); |
| 565 }; |
| 566 |
| 567 usbGnubby.prototype.ping = function(data, cb) { |
| 568 if (!cb) cb = usbGnubby.defaultCallback; |
| 569 if (typeof data == 'number') { |
| 570 var d = new Uint8Array(data); |
| 571 window.crypto.getRandomValues(d); |
| 572 data = d.buffer; |
| 573 } |
| 574 this.exchange_(llGnubby.CMD_PING, data, usbGnubby.NORMAL_TIMEOUT, cb); |
| 575 }; |
| 576 |
| 577 usbGnubby.prototype.apdu = function(data, cb) { |
| 578 if (!cb) cb = usbGnubby.defaultCallback; |
| 579 this.exchange_(llGnubby.CMD_APDU, data, usbGnubby.MAX_TIMEOUT, cb); |
| 580 }; |
| 581 |
| 582 usbGnubby.prototype.reset = function(cb) { |
| 583 if (!cb) cb = usbGnubby.defaultCallback; |
| 584 this.exchange_(llGnubby.CMD_ATR, new ArrayBuffer(0), |
| 585 usbGnubby.NORMAL_TIMEOUT, cb); |
| 586 }; |
| 587 |
| 588 // byte args[3] = [delay-in-ms before disabling interrupts, |
| 589 // delay-in-ms before disabling usb (aka remove), |
| 590 // delay-in-ms before reboot (aka insert)] |
| 591 usbGnubby.prototype.usb_test = function(args, cb) { |
| 592 if (!cb) cb = usbGnubby.defaultCallback; |
| 593 var u8 = new Uint8Array(args); |
| 594 this.exchange_(llGnubby.CMD_USB_TEST, u8.buffer, |
| 595 usbGnubby.NORMAL_TIMEOUT, cb); |
| 596 }; |
| 597 |
| 598 usbGnubby.prototype.apduReply_ = function(request, cb, opt_nowink) { |
| 599 if (!cb) cb = usbGnubby.defaultCallback; |
| 600 var self = this; |
| 601 |
| 602 this.apdu(request, function(rc, data) { |
| 603 if (rc == 0) { |
| 604 var r8 = new Uint8Array(data); |
| 605 if (r8[r8.length - 2] == 0x90 && r8[r8.length - 1] == 0x00) { |
| 606 // strip trailing 9000 |
| 607 var buf = new Uint8Array(r8.subarray(0, r8.length - 2)); |
| 608 cb(-llGnubby.OK, buf.buffer); |
| 609 return; |
| 610 } else { |
| 611 // return non-9000 as rc |
| 612 rc = r8[r8.length - 2] * 256 + r8[r8.length - 1]; |
| 613 // wink gnubby at hand if it needs touching. |
| 614 if (rc == 0x6985 && !opt_nowink) { |
| 615 self.wink(function() { cb(rc); }); |
| 616 return; |
| 617 } |
| 618 } |
| 619 } |
| 620 // Warn on errors other than waiting for touch, wrong data, and |
| 621 // unrecognized command. |
| 622 if (rc != 0x6985 && rc != 0x6a80 && rc != 0x6d00) { |
| 623 console.warn(UTIL_fmt('apduReply_ fail: ' + rc.toString(16))); |
| 624 } |
| 625 cb(rc); |
| 626 }); |
| 627 }; |
OLD | NEW |