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 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
432 } | 447 } |
433 | 448 |
434 // Start timeout timer. | 449 // Start timeout timer. |
435 tid = window.setTimeout(read_timeout, 1000.0 * timeout); | 450 tid = window.setTimeout(read_timeout, 1000.0 * timeout); |
436 | 451 |
437 // Schedule read of first frame. | 452 // Schedule read of first frame. |
438 self.notifyFrame_(init_frame); | 453 self.notifyFrame_(init_frame); |
439 }; | 454 }; |
440 | 455 |
441 /** | 456 /** |
442 * @const | 457 * @const |
443 */ | 458 */ |
444 Gnubby.NOTIFICATION_CID = 0; | 459 Gnubby.NOTIFICATION_CID = 0; |
445 | 460 |
446 /** | 461 /** |
447 * @const | 462 * @const |
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 |