| OLD | NEW |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * @fileoverview Provides a client view of a gnubby, aka USB security key. | 6 * @fileoverview Provides a client view of a gnubby, aka USB security key. |
| 7 */ | 7 */ |
| 8 'use strict'; | 8 'use strict'; |
| 9 | 9 |
| 10 /** | 10 /** |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 42 /** @private {Gnubbies} */ | 42 /** @private {Gnubbies} */ |
| 43 Gnubby.gnubbies_ = gnubbies; | 43 Gnubby.gnubbies_ = gnubbies; |
| 44 }; | 44 }; |
| 45 | 45 |
| 46 /** | 46 /** |
| 47 * Return cid as hex string. | 47 * Return cid as hex string. |
| 48 * @param {number} cid to convert. | 48 * @param {number} cid to convert. |
| 49 * @return {string} hexadecimal string. | 49 * @return {string} hexadecimal string. |
| 50 */ | 50 */ |
| 51 Gnubby.hexCid = function(cid) { | 51 Gnubby.hexCid = function(cid) { |
| 52 var tmp = [(cid >>> 24) & 255, | 52 var tmp = [ |
| 53 (cid >>> 16) & 255, | 53 (cid >>> 24) & 255, (cid >>> 16) & 255, (cid >>> 8) & 255, (cid >>> 0) & 255 |
| 54 (cid >>> 8) & 255, | 54 ]; |
| 55 (cid >>> 0) & 255]; | |
| 56 return UTIL_BytesToHex(tmp); | 55 return UTIL_BytesToHex(tmp); |
| 57 }; | 56 }; |
| 58 | 57 |
| 59 /** | 58 /** |
| 60 * Cancels open attempt for this gnubby, if available. | 59 * Cancels open attempt for this gnubby, if available. |
| 61 */ | 60 */ |
| 62 Gnubby.prototype.cancelOpen = function() { | 61 Gnubby.prototype.cancelOpen = function() { |
| 63 if (this.which) | 62 if (this.which) |
| 64 Gnubby.gnubbies_.cancelAddClient(this.which); | 63 Gnubby.gnubbies_.cancelAddClient(this.which); |
| 65 }; | 64 }; |
| (...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 165 if (this.dev) { | 164 if (this.dev) { |
| 166 console.log(UTIL_fmt('Gnubby.close()')); | 165 console.log(UTIL_fmt('Gnubby.close()')); |
| 167 this.rxframes = []; | 166 this.rxframes = []; |
| 168 this.rxcb = null; | 167 this.rxcb = null; |
| 169 var dev = this.dev; | 168 var dev = this.dev; |
| 170 this.dev = null; | 169 this.dev = null; |
| 171 var self = this; | 170 var self = this; |
| 172 // Wait a bit in case simpleton client tries open next gnubby. | 171 // Wait a bit in case simpleton client tries open next gnubby. |
| 173 // Without delay, gnubbies would drop all idle devices, before client | 172 // Without delay, gnubbies would drop all idle devices, before client |
| 174 // gets to the next one. | 173 // gets to the next one. |
| 175 window.setTimeout( | 174 window.setTimeout(function() { |
| 176 function() { | 175 Gnubby.gnubbies_.removeClient(dev, self); |
| 177 Gnubby.gnubbies_.removeClient(dev, self); | 176 }, 300); |
| 178 }, 300); | |
| 179 } | 177 } |
| 180 }; | 178 }; |
| 181 | 179 |
| 182 /** | 180 /** |
| 183 * Asks this gnubby to close when it gets a chance. | 181 * Asks this gnubby to close when it gets a chance. |
| 184 * @param {Function=} cb called back when closed. | 182 * @param {Function=} cb called back when closed. |
| 185 */ | 183 */ |
| 186 Gnubby.prototype.closeWhenIdle = function(cb) { | 184 Gnubby.prototype.closeWhenIdle = function(cb) { |
| 187 if (!this.inUse_()) { | 185 if (!this.inUse_()) { |
| 188 this.close(); | 186 this.close(); |
| 189 if (cb) cb(); | 187 if (cb) |
| 188 cb(); |
| 190 return; | 189 return; |
| 191 } | 190 } |
| 192 this.closingWhenIdle = true; | 191 this.closingWhenIdle = true; |
| 193 if (cb) this.notifyOnClose.push(cb); | 192 if (cb) |
| 193 this.notifyOnClose.push(cb); |
| 194 }; | 194 }; |
| 195 | 195 |
| 196 /** | 196 /** |
| 197 * Sets a callback that will get called when this gnubby is closed. | 197 * Sets a callback that will get called when this gnubby is closed. |
| 198 * @param {function() : ?Promise} cb Called back when closed. Callback | 198 * @param {function() : ?Promise} cb Called back when closed. Callback |
| 199 * may yield a promise that resolves when the close hook completes. | 199 * may yield a promise that resolves when the close hook completes. |
| 200 */ | 200 */ |
| 201 Gnubby.prototype.setCloseHook = function(cb) { | 201 Gnubby.prototype.setCloseHook = function(cb) { |
| 202 this.closeHook_ = cb; | 202 this.closeHook_ = cb; |
| 203 }; | 203 }; |
| (...skipping 11 matching lines...) Expand all Loading... |
| 215 }; | 215 }; |
| 216 | 216 |
| 217 /** | 217 /** |
| 218 * Notify callback for every frame received. | 218 * Notify callback for every frame received. |
| 219 * @param {function()} cb Callback | 219 * @param {function()} cb Callback |
| 220 * @private | 220 * @private |
| 221 */ | 221 */ |
| 222 Gnubby.prototype.notifyFrame_ = function(cb) { | 222 Gnubby.prototype.notifyFrame_ = function(cb) { |
| 223 if (this.rxframes.length != 0) { | 223 if (this.rxframes.length != 0) { |
| 224 // Already have frames; continue. | 224 // Already have frames; continue. |
| 225 if (cb) window.setTimeout(cb, 0); | 225 if (cb) |
| 226 window.setTimeout(cb, 0); |
| 226 } else { | 227 } else { |
| 227 this.rxcb = cb; | 228 this.rxcb = cb; |
| 228 } | 229 } |
| 229 }; | 230 }; |
| 230 | 231 |
| 231 /** | 232 /** |
| 232 * Called by low level driver with a frame. | 233 * Called by low level driver with a frame. |
| 233 * @param {ArrayBuffer|Uint8Array} frame Data frame | 234 * @param {ArrayBuffer|Uint8Array} frame Data frame |
| 234 * @return {boolean} Whether this client is still interested in receiving | 235 * @return {boolean} Whether this client is still interested in receiving |
| 235 * frames from its device. | 236 * frames from its device. |
| 236 */ | 237 */ |
| 237 Gnubby.prototype.receivedFrame = function(frame) { | 238 Gnubby.prototype.receivedFrame = function(frame) { |
| 238 if (this.closed) return false; // No longer interested. | 239 if (this.closed) |
| 240 return false; // No longer interested. |
| 239 | 241 |
| 240 if (!this.checkCID_(frame)) { | 242 if (!this.checkCID_(frame)) { |
| 241 // Not for me, ignore. | 243 // Not for me, ignore. |
| 242 return true; | 244 return true; |
| 243 } | 245 } |
| 244 | 246 |
| 245 this.rxframes.push(frame); | 247 this.rxframes.push(frame); |
| 246 | 248 |
| 247 // Callback self in case we were waiting. Once. | 249 // Callback self in case we were waiting. Once. |
| 248 var cb = this.rxcb; | 250 var cb = this.rxcb; |
| 249 this.rxcb = null; | 251 this.rxcb = null; |
| 250 if (cb) window.setTimeout(cb, 0); | 252 if (cb) |
| 253 window.setTimeout(cb, 0); |
| 251 | 254 |
| 252 return true; | 255 return true; |
| 253 }; | 256 }; |
| 254 | 257 |
| 255 /** | 258 /** |
| 256 * @return {number|undefined} The last read error seen by this device. | 259 * @return {number|undefined} The last read error seen by this device. |
| 257 */ | 260 */ |
| 258 Gnubby.prototype.getLastReadError = function() { | 261 Gnubby.prototype.getLastReadError = function() { |
| 259 return this.lastReadError_; | 262 return this.lastReadError_; |
| 260 }; | 263 }; |
| 261 | 264 |
| 262 /** | 265 /** |
| 263 * @return {ArrayBuffer|Uint8Array} oldest received frame. Throw if none. | 266 * @return {ArrayBuffer|Uint8Array} oldest received frame. Throw if none. |
| 264 * @private | 267 * @private |
| 265 */ | 268 */ |
| 266 Gnubby.prototype.readFrame_ = function() { | 269 Gnubby.prototype.readFrame_ = function() { |
| 267 if (this.rxframes.length == 0) throw 'rxframes empty!'; | 270 if (this.rxframes.length == 0) |
| 271 throw 'rxframes empty!'; |
| 268 | 272 |
| 269 var frame = this.rxframes.shift(); | 273 var frame = this.rxframes.shift(); |
| 270 return frame; | 274 return frame; |
| 271 }; | 275 }; |
| 272 | 276 |
| 273 /** Poll from rxframes[]. | 277 /** Poll from rxframes[]. |
| 274 * @param {number} cmd Command | 278 * @param {number} cmd Command |
| 275 * @param {number} timeout timeout in seconds. | 279 * @param {number} timeout timeout in seconds. |
| 276 * @param {?function(...)} cb Callback | 280 * @param {?function(...)} cb Callback |
| 277 * @private | 281 * @private |
| 278 */ | 282 */ |
| 279 Gnubby.prototype.read_ = function(cmd, timeout, cb) { | 283 Gnubby.prototype.read_ = function(cmd, timeout, cb) { |
| 280 if (this.closed) { cb(-GnubbyDevice.GONE); return; } | 284 if (this.closed) { |
| 281 if (!this.dev) { cb(-GnubbyDevice.GONE); return; } | 285 cb(-GnubbyDevice.GONE); |
| 286 return; |
| 287 } |
| 288 if (!this.dev) { |
| 289 cb(-GnubbyDevice.GONE); |
| 290 return; |
| 291 } |
| 282 | 292 |
| 283 var tid = null; // timeout timer id. | 293 var tid = null; // timeout timer id. |
| 284 var callback = cb; | 294 var callback = cb; |
| 285 var self = this; | 295 var self = this; |
| 286 | 296 |
| 287 var msg = null; | 297 var msg = null; |
| 288 var seqno = 0; | 298 var seqno = 0; |
| 289 var count = 0; | 299 var count = 0; |
| 290 | 300 |
| 291 /** | 301 /** |
| 292 * Schedule call to cb if not called yet. | 302 * Schedule call to cb if not called yet. |
| 293 * @param {number} a Return code. | 303 * @param {number} a Return code. |
| 294 * @param {Object=} b Optional data. | 304 * @param {Object=} b Optional data. |
| 295 */ | 305 */ |
| 296 function schedule_cb(a, b) { | 306 function schedule_cb(a, b) { |
| 297 self.commandPending = false; | 307 self.commandPending = false; |
| 298 if (tid) { | 308 if (tid) { |
| 299 // Cancel timeout timer. | 309 // Cancel timeout timer. |
| 300 window.clearTimeout(tid); | 310 window.clearTimeout(tid); |
| 301 tid = null; | 311 tid = null; |
| 302 } | 312 } |
| 303 self.lastReadError_ = /** @private {number|undefined} */ (a); | 313 self.lastReadError_ = /** @private {number|undefined} */ (a); |
| 304 var c = callback; | 314 var c = callback; |
| 305 if (c) { | 315 if (c) { |
| 306 callback = null; | 316 callback = null; |
| 307 window.setTimeout(function() { c(a, b); }, 0); | 317 window.setTimeout(function() { |
| 318 c(a, b); |
| 319 }, 0); |
| 308 } | 320 } |
| 309 if (self.closingWhenIdle) self.idleClose_(); | 321 if (self.closingWhenIdle) |
| 322 self.idleClose_(); |
| 310 }; | 323 }; |
| 311 | 324 |
| 312 function read_timeout() { | 325 function read_timeout() { |
| 313 if (!callback || !tid) return; // Already done. | 326 if (!callback || !tid) |
| 327 return; // Already done. |
| 314 | 328 |
| 315 console.error(UTIL_fmt( | 329 console.error(UTIL_fmt('[' + Gnubby.hexCid(self.cid) + '] timeout!')); |
| 316 '[' + Gnubby.hexCid(self.cid) + '] timeout!')); | |
| 317 | 330 |
| 318 if (self.dev) { | 331 if (self.dev) { |
| 319 self.dev.destroy(); // Stop pretending this thing works. | 332 self.dev.destroy(); // Stop pretending this thing works. |
| 320 } | 333 } |
| 321 | 334 |
| 322 tid = null; | 335 tid = null; |
| 323 | 336 |
| 324 schedule_cb(-GnubbyDevice.TIMEOUT); | 337 schedule_cb(-GnubbyDevice.TIMEOUT); |
| 325 }; | 338 }; |
| 326 | 339 |
| 327 function cont_frame() { | 340 function cont_frame() { |
| 328 if (!callback || !tid) return; // Already done. | 341 if (!callback || !tid) |
| 342 return; // Already done. |
| 329 | 343 |
| 330 var f = new Uint8Array(self.readFrame_()); | 344 var f = new Uint8Array(self.readFrame_()); |
| 331 var rcmd = f[4]; | 345 var rcmd = f[4]; |
| 332 var totalLen = (f[5] << 8) + f[6]; | 346 var totalLen = (f[5] << 8) + f[6]; |
| 333 | 347 |
| 334 if (rcmd == GnubbyDevice.CMD_ERROR && totalLen == 1) { | 348 if (rcmd == GnubbyDevice.CMD_ERROR && totalLen == 1) { |
| 335 // Error from device; forward. | 349 // Error from device; forward. |
| 336 console.log(UTIL_fmt( | 350 console.log(UTIL_fmt( |
| 337 '[' + Gnubby.hexCid(self.cid) + '] error frame ' + | 351 '[' + Gnubby.hexCid(self.cid) + '] error frame ' + |
| 338 UTIL_BytesToHex(f))); | 352 UTIL_BytesToHex(f))); |
| (...skipping 30 matching lines...) Expand all Loading... |
| 369 if (count == msg.length) { | 383 if (count == msg.length) { |
| 370 // Done. | 384 // Done. |
| 371 schedule_cb(-GnubbyDevice.OK, msg.buffer); | 385 schedule_cb(-GnubbyDevice.OK, msg.buffer); |
| 372 } else { | 386 } else { |
| 373 // Need more CONT frame(s). | 387 // Need more CONT frame(s). |
| 374 self.notifyFrame_(cont_frame); | 388 self.notifyFrame_(cont_frame); |
| 375 } | 389 } |
| 376 } | 390 } |
| 377 | 391 |
| 378 function init_frame() { | 392 function init_frame() { |
| 379 if (!callback || !tid) return; // Already done. | 393 if (!callback || !tid) |
| 394 return; // Already done. |
| 380 | 395 |
| 381 var f = new Uint8Array(self.readFrame_()); | 396 var f = new Uint8Array(self.readFrame_()); |
| 382 | 397 |
| 383 var rcmd = f[4]; | 398 var rcmd = f[4]; |
| 384 var totalLen = (f[5] << 8) + f[6]; | 399 var totalLen = (f[5] << 8) + f[6]; |
| 385 | 400 |
| 386 if (rcmd == GnubbyDevice.CMD_ERROR && totalLen == 1) { | 401 if (rcmd == GnubbyDevice.CMD_ERROR && totalLen == 1) { |
| 387 // Error from device; forward. | 402 // Error from device; forward. |
| 388 // Don't log busy frames, they're "normal". | 403 // Don't log busy frames, they're "normal". |
| 389 if (f[7] != GnubbyDevice.BUSY) { | 404 if (f[7] != GnubbyDevice.BUSY) { |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 448 */ | 463 */ |
| 449 Gnubby.BROADCAST_CID = (0xff << 24) | (0xff << 16) | (0xff << 8) | 0xff; | 464 Gnubby.BROADCAST_CID = (0xff << 24) | (0xff << 16) | (0xff << 8) | 0xff; |
| 450 | 465 |
| 451 /** | 466 /** |
| 452 * @param {ArrayBuffer|Uint8Array} frame Data frame | 467 * @param {ArrayBuffer|Uint8Array} frame Data frame |
| 453 * @return {boolean} Whether frame is for my channel. | 468 * @return {boolean} Whether frame is for my channel. |
| 454 * @private | 469 * @private |
| 455 */ | 470 */ |
| 456 Gnubby.prototype.checkCID_ = function(frame) { | 471 Gnubby.prototype.checkCID_ = function(frame) { |
| 457 var f = new Uint8Array(frame); | 472 var f = new Uint8Array(frame); |
| 458 var c = (f[0] << 24) | | 473 var c = (f[0] << 24) | (f[1] << 16) | (f[2] << 8) | (f[3]); |
| 459 (f[1] << 16) | | 474 return c === this.cid || c === Gnubby.NOTIFICATION_CID; |
| 460 (f[2] << 8) | | |
| 461 (f[3]); | |
| 462 return c === this.cid || | |
| 463 c === Gnubby.NOTIFICATION_CID; | |
| 464 }; | 475 }; |
| 465 | 476 |
| 466 /** | 477 /** |
| 467 * Queue command for sending. | 478 * Queue command for sending. |
| 468 * @param {number} cmd The command to send. | 479 * @param {number} cmd The command to send. |
| 469 * @param {ArrayBuffer|Uint8Array} data Command data | 480 * @param {ArrayBuffer|Uint8Array} data Command data |
| 470 * @private | 481 * @private |
| 471 */ | 482 */ |
| 472 Gnubby.prototype.write_ = function(cmd, data) { | 483 Gnubby.prototype.write_ = function(cmd, data) { |
| 473 if (this.closed) return; | 484 if (this.closed) |
| 474 if (!this.dev) return; | 485 return; |
| 486 if (!this.dev) |
| 487 return; |
| 475 | 488 |
| 476 this.commandPending = true; | 489 this.commandPending = true; |
| 477 | 490 |
| 478 this.dev.queueCommand(this.cid, cmd, data); | 491 this.dev.queueCommand(this.cid, cmd, data); |
| 479 }; | 492 }; |
| 480 | 493 |
| 481 /** | 494 /** |
| 482 * Writes the command, and calls back when the command's reply is received. | 495 * Writes the command, and calls back when the command's reply is received. |
| 483 * @param {number} cmd The command to send. | 496 * @param {number} cmd The command to send. |
| 484 * @param {ArrayBuffer|Uint8Array} data Command data | 497 * @param {ArrayBuffer|Uint8Array} data Command data |
| (...skipping 28 matching lines...) Expand all Loading... |
| 513 */ | 526 */ |
| 514 Gnubby.SYS_TIMER_ = new WindowTimer(); | 527 Gnubby.SYS_TIMER_ = new WindowTimer(); |
| 515 | 528 |
| 516 /** Default callback for commands. Simply logs to console. | 529 /** Default callback for commands. Simply logs to console. |
| 517 * @param {number} rc Result status code | 530 * @param {number} rc Result status code |
| 518 * @param {(ArrayBuffer|Uint8Array|Array<number>|null)} data Result data | 531 * @param {(ArrayBuffer|Uint8Array|Array<number>|null)} data Result data |
| 519 */ | 532 */ |
| 520 Gnubby.defaultCallback = function(rc, data) { | 533 Gnubby.defaultCallback = function(rc, data) { |
| 521 var msg = 'defaultCallback(' + rc; | 534 var msg = 'defaultCallback(' + rc; |
| 522 if (data) { | 535 if (data) { |
| 523 if (typeof data == 'string') msg += ', ' + data; | 536 if (typeof data == 'string') |
| 524 else msg += ', ' + UTIL_BytesToHex(new Uint8Array(data)); | 537 msg += ', ' + data; |
| 538 else |
| 539 msg += ', ' + UTIL_BytesToHex(new Uint8Array(data)); |
| 525 } | 540 } |
| 526 msg += ')'; | 541 msg += ')'; |
| 527 console.log(UTIL_fmt(msg)); | 542 console.log(UTIL_fmt(msg)); |
| 528 }; | 543 }; |
| 529 | 544 |
| 530 /** | 545 /** |
| 531 * Ensures this device has temporary ownership of the USB device, by: | 546 * Ensures this device has temporary ownership of the USB device, by: |
| 532 * 1. Using the INIT command to allocate an unique channel id, if one hasn't | 547 * 1. Using the INIT command to allocate an unique channel id, if one hasn't |
| 533 * been retrieved before, or | 548 * been retrieved before, or |
| 534 * 2. Sending a nonce to device, flushing read queue until match. | 549 * 2. Sending a nonce to device, flushing read queue until match. |
| 535 * @param {?function(...)} cb Callback | 550 * @param {?function(...)} cb Callback |
| 536 */ | 551 */ |
| 537 Gnubby.prototype.sync = function(cb) { | 552 Gnubby.prototype.sync = function(cb) { |
| 538 if (!cb) cb = Gnubby.defaultCallback; | 553 if (!cb) |
| 554 cb = Gnubby.defaultCallback; |
| 539 if (this.closed) { | 555 if (this.closed) { |
| 540 cb(-GnubbyDevice.GONE); | 556 cb(-GnubbyDevice.GONE); |
| 541 return; | 557 return; |
| 542 } | 558 } |
| 543 | 559 |
| 544 var done = false; | 560 var done = false; |
| 545 var trycount = 6; | 561 var trycount = 6; |
| 546 var tid = null; | 562 var tid = null; |
| 547 var self = this; | 563 var self = this; |
| 548 | 564 |
| 549 function returnValue(rc) { | 565 function returnValue(rc) { |
| 550 done = true; | 566 done = true; |
| 551 window.setTimeout(cb.bind(null, rc), 0); | 567 window.setTimeout(cb.bind(null, rc), 0); |
| 552 if (self.closingWhenIdle) self.idleClose_(); | 568 if (self.closingWhenIdle) |
| 569 self.idleClose_(); |
| 553 } | 570 } |
| 554 | 571 |
| 555 function callback(rc, opt_frame) { | 572 function callback(rc, opt_frame) { |
| 556 self.commandPending = false; | 573 self.commandPending = false; |
| 557 if (tid) { | 574 if (tid) { |
| 558 window.clearTimeout(tid); | 575 window.clearTimeout(tid); |
| 559 tid = null; | 576 tid = null; |
| 560 } | 577 } |
| 561 completionAction(rc, opt_frame); | 578 completionAction(rc, opt_frame); |
| 562 } | 579 } |
| 563 | 580 |
| 564 function sendSyncSentinel() { | 581 function sendSyncSentinel() { |
| 565 var cmd = GnubbyDevice.CMD_SYNC; | 582 var cmd = GnubbyDevice.CMD_SYNC; |
| 566 var data = new Uint8Array(1); | 583 var data = new Uint8Array(1); |
| 567 data[0] = ++self.synccnt; | 584 data[0] = ++self.synccnt; |
| 568 self.dev.queueCommand(self.cid, cmd, data.buffer); | 585 self.dev.queueCommand(self.cid, cmd, data.buffer); |
| 569 } | 586 } |
| 570 | 587 |
| 571 function syncSentinelEquals(f) { | 588 function syncSentinelEquals(f) { |
| 572 return (f[4] == GnubbyDevice.CMD_SYNC && | 589 return ( |
| 590 f[4] == GnubbyDevice.CMD_SYNC && |
| 573 (f.length == 7 || /* fw pre-0.2.1 bug: does not echo sentinel */ | 591 (f.length == 7 || /* fw pre-0.2.1 bug: does not echo sentinel */ |
| 574 f[7] == self.synccnt)); | 592 f[7] == self.synccnt)); |
| 575 } | 593 } |
| 576 | 594 |
| 577 function syncCompletionAction(rc, opt_frame) { | 595 function syncCompletionAction(rc, opt_frame) { |
| 578 if (rc) console.warn(UTIL_fmt('sync failed: ' + rc)); | 596 if (rc) |
| 597 console.warn(UTIL_fmt('sync failed: ' + rc)); |
| 579 returnValue(rc); | 598 returnValue(rc); |
| 580 } | 599 } |
| 581 | 600 |
| 582 function sendInitSentinel() { | 601 function sendInitSentinel() { |
| 583 var cid = self.cid; | 602 var cid = self.cid; |
| 584 // If we do not have a specific CID yet, reset to BROADCAST for init. | 603 // If we do not have a specific CID yet, reset to BROADCAST for init. |
| 585 if (self.cid == Gnubby.defaultChannelId_(self.gnubbyInstance, self.which)) { | 604 if (self.cid == Gnubby.defaultChannelId_(self.gnubbyInstance, self.which)) { |
| 586 self.cid = Gnubby.BROADCAST_CID; | 605 self.cid = Gnubby.BROADCAST_CID; |
| 587 cid = self.cid; | 606 cid = self.cid; |
| 588 } | 607 } |
| 589 var cmd = GnubbyDevice.CMD_INIT; | 608 var cmd = GnubbyDevice.CMD_INIT; |
| 590 self.dev.queueCommand(cid, cmd, nonce); | 609 self.dev.queueCommand(cid, cmd, nonce); |
| 591 } | 610 } |
| 592 | 611 |
| 593 function initSentinelEquals(f) { | 612 function initSentinelEquals(f) { |
| 594 return (f[4] == GnubbyDevice.CMD_INIT && | 613 return ( |
| 595 f.length >= nonce.length + 7 && | 614 f[4] == GnubbyDevice.CMD_INIT && f.length >= nonce.length + 7 && |
| 596 UTIL_equalArrays(f.subarray(7, nonce.length + 7), nonce)); | 615 UTIL_equalArrays(f.subarray(7, nonce.length + 7), nonce)); |
| 597 } | 616 } |
| 598 | 617 |
| 599 function initCmdUnsupported(rc) { | 618 function initCmdUnsupported(rc) { |
| 600 // Different firmwares fail differently on different inputs, so treat any | 619 // Different firmwares fail differently on different inputs, so treat any |
| 601 // of the following errors as indicating the INIT command isn't supported. | 620 // of the following errors as indicating the INIT command isn't supported. |
| 602 return rc == -GnubbyDevice.INVALID_CMD || | 621 return rc == -GnubbyDevice.INVALID_CMD || rc == -GnubbyDevice.INVALID_PAR || |
| 603 rc == -GnubbyDevice.INVALID_PAR || | |
| 604 rc == -GnubbyDevice.INVALID_LEN; | 622 rc == -GnubbyDevice.INVALID_LEN; |
| 605 } | 623 } |
| 606 | 624 |
| 607 function initCompletionAction(rc, opt_frame) { | 625 function initCompletionAction(rc, opt_frame) { |
| 608 // Actual failures: bail out. | 626 // Actual failures: bail out. |
| 609 if (rc && !initCmdUnsupported(rc)) { | 627 if (rc && !initCmdUnsupported(rc)) { |
| 610 console.warn(UTIL_fmt('init failed: ' + rc)); | 628 console.warn(UTIL_fmt('init failed: ' + rc)); |
| 611 returnValue(rc); | 629 returnValue(rc); |
| 612 } | 630 } |
| 613 | 631 |
| 614 var HEADER_LENGTH = 7; | 632 var HEADER_LENGTH = 7; |
| 615 var MIN_LENGTH = HEADER_LENGTH + 4; // 4 bytes for the channel id | 633 var MIN_LENGTH = HEADER_LENGTH + 4; // 4 bytes for the channel id |
| 616 if (rc || !opt_frame || opt_frame.length < nonce.length + MIN_LENGTH) { | 634 if (rc || !opt_frame || opt_frame.length < nonce.length + MIN_LENGTH) { |
| 617 // INIT command not supported or is missing the returned channel id: | 635 // INIT command not supported or is missing the returned channel id: |
| 618 // Pick a random cid to try to prevent collisions on the USB bus. | 636 // Pick a random cid to try to prevent collisions on the USB bus. |
| 619 var rnd = UTIL_getRandom(2); | 637 var rnd = UTIL_getRandom(2); |
| 620 self.cid = Gnubby.defaultChannelId_(self.gnubbyInstance, self.which); | 638 self.cid = Gnubby.defaultChannelId_(self.gnubbyInstance, self.which); |
| 621 self.cid ^= (rnd[0] << 16) | (rnd[1] << 8); | 639 self.cid ^= (rnd[0] << 16) | (rnd[1] << 8); |
| 622 // Now sync with that cid, to make sure we've got it. | 640 // Now sync with that cid, to make sure we've got it. |
| 623 setSync(); | 641 setSync(); |
| 624 timeoutLoop(); | 642 timeoutLoop(); |
| 625 return; | 643 return; |
| 626 } | 644 } |
| 627 // Accept the provided cid. | 645 // Accept the provided cid. |
| 628 var offs = HEADER_LENGTH + nonce.length; | 646 var offs = HEADER_LENGTH + nonce.length; |
| 629 self.cid = (opt_frame[offs] << 24) | | 647 self.cid = (opt_frame[offs] << 24) | (opt_frame[offs + 1] << 16) | |
| 630 (opt_frame[offs + 1] << 16) | | 648 (opt_frame[offs + 2] << 8) | opt_frame[offs + 3]; |
| 631 (opt_frame[offs + 2] << 8) | | |
| 632 opt_frame[offs + 3]; | |
| 633 returnValue(rc); | 649 returnValue(rc); |
| 634 } | 650 } |
| 635 | 651 |
| 636 function checkSentinel() { | 652 function checkSentinel() { |
| 637 var f = new Uint8Array(self.readFrame_()); | 653 var f = new Uint8Array(self.readFrame_()); |
| 638 | 654 |
| 639 // Stop on errors and return them. | 655 // Stop on errors and return them. |
| 640 if (f[4] == GnubbyDevice.CMD_ERROR && | 656 if (f[4] == GnubbyDevice.CMD_ERROR && f[5] == 0 && f[6] == 1) { |
| 641 f[5] == 0 && f[6] == 1) { | |
| 642 if (f[7] == GnubbyDevice.BUSY) { | 657 if (f[7] == GnubbyDevice.BUSY) { |
| 643 // Not spec but some devices do this; retry. | 658 // Not spec but some devices do this; retry. |
| 644 sendSentinel(); | 659 sendSentinel(); |
| 645 self.notifyFrame_(checkSentinel); | 660 self.notifyFrame_(checkSentinel); |
| 646 return; | 661 return; |
| 647 } | 662 } |
| 648 if (f[7] == GnubbyDevice.GONE) { | 663 if (f[7] == GnubbyDevice.GONE) { |
| 649 // Device disappeared on us. | 664 // Device disappeared on us. |
| 650 self.closed = true; | 665 self.closed = true; |
| 651 } | 666 } |
| 652 callback(-f[7]); | 667 callback(-f[7]); |
| 653 return; | 668 return; |
| 654 } | 669 } |
| 655 | 670 |
| 656 // Eat everything else but expected sentinel reply. | 671 // Eat everything else but expected sentinel reply. |
| 657 if (!sentinelEquals(f)) { | 672 if (!sentinelEquals(f)) { |
| 658 // Read more. | 673 // Read more. |
| 659 self.notifyFrame_(checkSentinel); | 674 self.notifyFrame_(checkSentinel); |
| 660 return; | 675 return; |
| 661 } | 676 } |
| 662 | 677 |
| 663 // Done. | 678 // Done. |
| 664 callback(-GnubbyDevice.OK, f); | 679 callback(-GnubbyDevice.OK, f); |
| 665 }; | 680 }; |
| 666 | 681 |
| 667 function timeoutLoop() { | 682 function timeoutLoop() { |
| 668 if (done) return; | 683 if (done) |
| 684 return; |
| 669 | 685 |
| 670 if (trycount == 0) { | 686 if (trycount == 0) { |
| 671 // Failed. | 687 // Failed. |
| 672 callback(-GnubbyDevice.TIMEOUT); | 688 callback(-GnubbyDevice.TIMEOUT); |
| 673 return; | 689 return; |
| 674 } | 690 } |
| 675 | 691 |
| 676 --trycount; // Try another one. | 692 --trycount; // Try another one. |
| 677 sendSentinel(); | 693 sendSentinel(); |
| 678 self.notifyFrame_(checkSentinel); | 694 self.notifyFrame_(checkSentinel); |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 713 // Make our application level tolerance a little longer. | 729 // Make our application level tolerance a little longer. |
| 714 /** Maximum timeout in seconds */ | 730 /** Maximum timeout in seconds */ |
| 715 Gnubby.MAX_TIMEOUT = 31; | 731 Gnubby.MAX_TIMEOUT = 31; |
| 716 | 732 |
| 717 /** Blink led | 733 /** Blink led |
| 718 * @param {number|ArrayBuffer|Uint8Array} data Command data or number | 734 * @param {number|ArrayBuffer|Uint8Array} data Command data or number |
| 719 * of seconds to blink | 735 * of seconds to blink |
| 720 * @param {?function(...)} cb Callback | 736 * @param {?function(...)} cb Callback |
| 721 */ | 737 */ |
| 722 Gnubby.prototype.blink = function(data, cb) { | 738 Gnubby.prototype.blink = function(data, cb) { |
| 723 if (!cb) cb = Gnubby.defaultCallback; | 739 if (!cb) |
| 740 cb = Gnubby.defaultCallback; |
| 724 if (typeof data == 'number') { | 741 if (typeof data == 'number') { |
| 725 var d = new Uint8Array([data]); | 742 var d = new Uint8Array([data]); |
| 726 data = d.buffer; | 743 data = d.buffer; |
| 727 } | 744 } |
| 728 this.exchange_(GnubbyDevice.CMD_PROMPT, data, Gnubby.NORMAL_TIMEOUT, cb); | 745 this.exchange_(GnubbyDevice.CMD_PROMPT, data, Gnubby.NORMAL_TIMEOUT, cb); |
| 729 }; | 746 }; |
| 730 | 747 |
| 731 /** Lock the gnubby | 748 /** Lock the gnubby |
| 732 * @param {number|ArrayBuffer|Uint8Array} data Command data | 749 * @param {number|ArrayBuffer|Uint8Array} data Command data |
| 733 * @param {?function(...)} cb Callback | 750 * @param {?function(...)} cb Callback |
| 734 */ | 751 */ |
| 735 Gnubby.prototype.lock = function(data, cb) { | 752 Gnubby.prototype.lock = function(data, cb) { |
| 736 if (!cb) cb = Gnubby.defaultCallback; | 753 if (!cb) |
| 754 cb = Gnubby.defaultCallback; |
| 737 if (typeof data == 'number') { | 755 if (typeof data == 'number') { |
| 738 var d = new Uint8Array([data]); | 756 var d = new Uint8Array([data]); |
| 739 data = d.buffer; | 757 data = d.buffer; |
| 740 } | 758 } |
| 741 this.exchange_(GnubbyDevice.CMD_LOCK, data, Gnubby.NORMAL_TIMEOUT, cb); | 759 this.exchange_(GnubbyDevice.CMD_LOCK, data, Gnubby.NORMAL_TIMEOUT, cb); |
| 742 }; | 760 }; |
| 743 | 761 |
| 744 /** Unlock the gnubby | 762 /** Unlock the gnubby |
| 745 * @param {?function(...)} cb Callback | 763 * @param {?function(...)} cb Callback |
| 746 */ | 764 */ |
| 747 Gnubby.prototype.unlock = function(cb) { | 765 Gnubby.prototype.unlock = function(cb) { |
| 748 if (!cb) cb = Gnubby.defaultCallback; | 766 if (!cb) |
| 767 cb = Gnubby.defaultCallback; |
| 749 var data = new Uint8Array([0]); | 768 var data = new Uint8Array([0]); |
| 750 this.exchange_(GnubbyDevice.CMD_LOCK, data.buffer, | 769 this.exchange_(GnubbyDevice.CMD_LOCK, data.buffer, Gnubby.NORMAL_TIMEOUT, cb); |
| 751 Gnubby.NORMAL_TIMEOUT, cb); | |
| 752 }; | 770 }; |
| 753 | 771 |
| 754 /** Request system information data. | 772 /** Request system information data. |
| 755 * @param {?function(...)} cb Callback | 773 * @param {?function(...)} cb Callback |
| 756 */ | 774 */ |
| 757 Gnubby.prototype.sysinfo = function(cb) { | 775 Gnubby.prototype.sysinfo = function(cb) { |
| 758 if (!cb) cb = Gnubby.defaultCallback; | 776 if (!cb) |
| 759 this.exchange_(GnubbyDevice.CMD_SYSINFO, new ArrayBuffer(0), | 777 cb = Gnubby.defaultCallback; |
| 760 Gnubby.NORMAL_TIMEOUT, cb); | 778 this.exchange_( |
| 779 GnubbyDevice.CMD_SYSINFO, new ArrayBuffer(0), Gnubby.NORMAL_TIMEOUT, cb); |
| 761 }; | 780 }; |
| 762 | 781 |
| 763 /** Send wink command | 782 /** Send wink command |
| 764 * @param {?function(...)} cb Callback | 783 * @param {?function(...)} cb Callback |
| 765 */ | 784 */ |
| 766 Gnubby.prototype.wink = function(cb) { | 785 Gnubby.prototype.wink = function(cb) { |
| 767 if (!cb) cb = Gnubby.defaultCallback; | 786 if (!cb) |
| 768 this.exchange_(GnubbyDevice.CMD_WINK, new ArrayBuffer(0), | 787 cb = Gnubby.defaultCallback; |
| 769 Gnubby.NORMAL_TIMEOUT, cb); | 788 this.exchange_( |
| 789 GnubbyDevice.CMD_WINK, new ArrayBuffer(0), Gnubby.NORMAL_TIMEOUT, cb); |
| 770 }; | 790 }; |
| 771 | 791 |
| 772 /** Send DFU (Device firmware upgrade) command | 792 /** Send DFU (Device firmware upgrade) command |
| 773 * @param {ArrayBuffer|Uint8Array} data Command data | 793 * @param {ArrayBuffer|Uint8Array} data Command data |
| 774 * @param {?function(...)} cb Callback | 794 * @param {?function(...)} cb Callback |
| 775 */ | 795 */ |
| 776 Gnubby.prototype.dfu = function(data, cb) { | 796 Gnubby.prototype.dfu = function(data, cb) { |
| 777 if (!cb) cb = Gnubby.defaultCallback; | 797 if (!cb) |
| 798 cb = Gnubby.defaultCallback; |
| 778 this.exchange_(GnubbyDevice.CMD_DFU, data, Gnubby.NORMAL_TIMEOUT, cb); | 799 this.exchange_(GnubbyDevice.CMD_DFU, data, Gnubby.NORMAL_TIMEOUT, cb); |
| 779 }; | 800 }; |
| 780 | 801 |
| 781 /** Ping the gnubby | 802 /** Ping the gnubby |
| 782 * @param {number|ArrayBuffer|Uint8Array} data Command data | 803 * @param {number|ArrayBuffer|Uint8Array} data Command data |
| 783 * @param {?function(...)} cb Callback | 804 * @param {?function(...)} cb Callback |
| 784 */ | 805 */ |
| 785 Gnubby.prototype.ping = function(data, cb) { | 806 Gnubby.prototype.ping = function(data, cb) { |
| 786 if (!cb) cb = Gnubby.defaultCallback; | 807 if (!cb) |
| 808 cb = Gnubby.defaultCallback; |
| 787 if (typeof data == 'number') { | 809 if (typeof data == 'number') { |
| 788 var d = new Uint8Array(data); | 810 var d = new Uint8Array(data); |
| 789 window.crypto.getRandomValues(d); | 811 window.crypto.getRandomValues(d); |
| 790 data = d.buffer; | 812 data = d.buffer; |
| 791 } | 813 } |
| 792 this.exchange_(GnubbyDevice.CMD_PING, data, Gnubby.NORMAL_TIMEOUT, cb); | 814 this.exchange_(GnubbyDevice.CMD_PING, data, Gnubby.NORMAL_TIMEOUT, cb); |
| 793 }; | 815 }; |
| 794 | 816 |
| 795 /** Send a raw APDU command | 817 /** Send a raw APDU command |
| 796 * @param {ArrayBuffer|Uint8Array} data Command data | 818 * @param {ArrayBuffer|Uint8Array} data Command data |
| 797 * @param {?function(...)} cb Callback | 819 * @param {?function(...)} cb Callback |
| 798 */ | 820 */ |
| 799 Gnubby.prototype.apdu = function(data, cb) { | 821 Gnubby.prototype.apdu = function(data, cb) { |
| 800 if (!cb) cb = Gnubby.defaultCallback; | 822 if (!cb) |
| 823 cb = Gnubby.defaultCallback; |
| 801 this.exchange_(GnubbyDevice.CMD_APDU, data, Gnubby.MAX_TIMEOUT, cb); | 824 this.exchange_(GnubbyDevice.CMD_APDU, data, Gnubby.MAX_TIMEOUT, cb); |
| 802 }; | 825 }; |
| 803 | 826 |
| 804 /** Reset gnubby | 827 /** Reset gnubby |
| 805 * @param {?function(...)} cb Callback | 828 * @param {?function(...)} cb Callback |
| 806 */ | 829 */ |
| 807 Gnubby.prototype.reset = function(cb) { | 830 Gnubby.prototype.reset = function(cb) { |
| 808 if (!cb) cb = Gnubby.defaultCallback; | 831 if (!cb) |
| 809 this.exchange_(GnubbyDevice.CMD_ATR, new ArrayBuffer(0), | 832 cb = Gnubby.defaultCallback; |
| 810 Gnubby.MAX_TIMEOUT, cb); | 833 this.exchange_( |
| 834 GnubbyDevice.CMD_ATR, new ArrayBuffer(0), Gnubby.MAX_TIMEOUT, cb); |
| 811 }; | 835 }; |
| 812 | 836 |
| 813 // byte args[3] = [delay-in-ms before disabling interrupts, | 837 // byte args[3] = [delay-in-ms before disabling interrupts, |
| 814 // delay-in-ms before disabling usb (aka remove), | 838 // delay-in-ms before disabling usb (aka remove), |
| 815 // delay-in-ms before reboot (aka insert)] | 839 // delay-in-ms before reboot (aka insert)] |
| 816 /** Send usb test command | 840 /** Send usb test command |
| 817 * @param {ArrayBuffer|Uint8Array} args Command data | 841 * @param {ArrayBuffer|Uint8Array} args Command data |
| 818 * @param {?function(...)} cb Callback | 842 * @param {?function(...)} cb Callback |
| 819 */ | 843 */ |
| 820 Gnubby.prototype.usb_test = function(args, cb) { | 844 Gnubby.prototype.usb_test = function(args, cb) { |
| 821 if (!cb) cb = Gnubby.defaultCallback; | 845 if (!cb) |
| 846 cb = Gnubby.defaultCallback; |
| 822 var u8 = new Uint8Array(args); | 847 var u8 = new Uint8Array(args); |
| 823 this.exchange_(GnubbyDevice.CMD_USB_TEST, u8.buffer, | 848 this.exchange_( |
| 824 Gnubby.NORMAL_TIMEOUT, cb); | 849 GnubbyDevice.CMD_USB_TEST, u8.buffer, Gnubby.NORMAL_TIMEOUT, cb); |
| 825 }; | 850 }; |
| 826 | 851 |
| 827 /** APDU command with reply | 852 /** APDU command with reply |
| 828 * @param {ArrayBuffer|Uint8Array} request The request | 853 * @param {ArrayBuffer|Uint8Array} request The request |
| 829 * @param {?function(...)} cb Callback | 854 * @param {?function(...)} cb Callback |
| 830 * @param {boolean=} opt_nowink Do not wink | 855 * @param {boolean=} opt_nowink Do not wink |
| 831 */ | 856 */ |
| 832 Gnubby.prototype.apduReply = function(request, cb, opt_nowink) { | 857 Gnubby.prototype.apduReply = function(request, cb, opt_nowink) { |
| 833 if (!cb) cb = Gnubby.defaultCallback; | 858 if (!cb) |
| 859 cb = Gnubby.defaultCallback; |
| 834 var self = this; | 860 var self = this; |
| 835 | 861 |
| 836 this.apdu(request, function(rc, data) { | 862 this.apdu(request, function(rc, data) { |
| 837 if (rc == 0) { | 863 if (rc == 0) { |
| 838 var r8 = new Uint8Array(data); | 864 var r8 = new Uint8Array(data); |
| 839 if (r8[r8.length - 2] == 0x90 && r8[r8.length - 1] == 0x00) { | 865 if (r8[r8.length - 2] == 0x90 && r8[r8.length - 1] == 0x00) { |
| 840 // strip trailing 9000 | 866 // strip trailing 9000 |
| 841 var buf = new Uint8Array(r8.subarray(0, r8.length - 2)); | 867 var buf = new Uint8Array(r8.subarray(0, r8.length - 2)); |
| 842 cb(-GnubbyDevice.OK, buf.buffer); | 868 cb(-GnubbyDevice.OK, buf.buffer); |
| 843 return; | 869 return; |
| 844 } else { | 870 } else { |
| 845 // return non-9000 as rc | 871 // return non-9000 as rc |
| 846 rc = r8[r8.length - 2] * 256 + r8[r8.length - 1]; | 872 rc = r8[r8.length - 2] * 256 + r8[r8.length - 1]; |
| 847 // wink gnubby at hand if it needs touching. | 873 // wink gnubby at hand if it needs touching. |
| 848 if (rc == 0x6985 && !opt_nowink) { | 874 if (rc == 0x6985 && !opt_nowink) { |
| 849 self.wink(function() { cb(rc); }); | 875 self.wink(function() { |
| 876 cb(rc); |
| 877 }); |
| 850 return; | 878 return; |
| 851 } | 879 } |
| 852 } | 880 } |
| 853 } | 881 } |
| 854 // Warn on errors other than waiting for touch, wrong data, and | 882 // Warn on errors other than waiting for touch, wrong data, and |
| 855 // unrecognized command. | 883 // unrecognized command. |
| 856 if (rc != 0x6985 && rc != 0x6a80 && rc != 0x6d00) { | 884 if (rc != 0x6985 && rc != 0x6a80 && rc != 0x6d00) { |
| 857 console.warn(UTIL_fmt('apduReply_ fail: ' + rc.toString(16))); | 885 console.warn(UTIL_fmt('apduReply_ fail: ' + rc.toString(16))); |
| 858 } | 886 } |
| 859 cb(rc); | 887 cb(rc); |
| 860 }); | 888 }); |
| 861 }; | 889 }; |
| OLD | NEW |