Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(765)

Side by Side Diff: chrome/browser/resources/cryptotoken/llusbgnubby.js

Issue 249913002: FIDO U2F component extension (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Merge with HEAD Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 Implements a low-level gnubby driver based on chrome.usb.
7 */
8 'use strict';
9
10 /**
11 * Low level gnubby 'driver'. One per physical USB device.
12 * @param {Gnubbies} gnubbies The gnubbies instances this device is enumerated
13 * in.
14 * @param {!chrome.usb.ConnectionHandle} dev The device.
15 * @param {number} id The device's id.
16 * @param {number} inEndpoint The device's in endpoint.
17 * @param {number} outEndpoint The device's out endpoint.
18 * @constructor
19 * @implements {llGnubby}
20 */
21 function llUsbGnubby(gnubbies, dev, id, inEndpoint, outEndpoint) {
22 /** @private {Gnubbies} */
23 this.gnubbies_ = gnubbies;
24 this.dev = dev;
25 this.id = id;
26 this.inEndpoint = inEndpoint;
27 this.outEndpoint = outEndpoint;
28 this.txqueue = [];
29 this.clients = [];
30 this.lockCID = 0; // channel ID of client holding a lock, if != 0.
31 this.lockMillis = 0; // current lock period.
32 this.lockTID = null; // timer id of lock timeout.
33 this.closing = false; // device to be closed by receive loop.
34 this.updating = false; // device firmware is in final stage of updating.
35 this.inTransferPending = false;
36 this.outTransferPending = false;
37 }
38
39 /**
40 * Namespace for the llUsbGnubby implementation.
41 * @const
42 */
43 llUsbGnubby.NAMESPACE = 'usb';
44
45 /** Destroys this low-level device instance. */
46 llUsbGnubby.prototype.destroy = function() {
47 if (!this.dev) return; // Already dead.
48
49 this.closing = true;
50
51 console.log(UTIL_fmt('llUsbGnubby.destroy()'));
52
53 // Synthesize a close error frame to alert all clients,
54 // some of which might be in read state.
55 //
56 // Use magic CID 0 to address all.
57 this.publishFrame_(new Uint8Array([
58 0, 0, 0, 0, // broadcast CID
59 llGnubby.CMD_ERROR,
60 0, 1, // length
61 llGnubby.GONE]).buffer);
62
63 // Set all clients to closed status and remove them.
64 while (this.clients.length != 0) {
65 var client = this.clients.shift();
66 if (client) client.closed = true;
67 }
68
69 if (this.lockTID) {
70 window.clearTimeout(this.lockTID);
71 this.lockTID = null;
72 }
73
74 var dev = this.dev;
75 this.dev = null;
76
77 var self = this;
78
79 function onClosed() {
80 console.log(UTIL_fmt('Device ' + dev.handle + ' closed'));
81 self.gnubbies_.removeOpenDevice(
82 {namespace: llUsbGnubby.NAMESPACE, device: self.id});
83 }
84
85 // Release first.
86 chrome.usb.releaseInterface(dev, 0, function() {
87 console.log(UTIL_fmt('Device ' + dev.handle + ' released'));
88 chrome.usb.closeDevice(dev, onClosed);
89 });
90 };
91
92 /**
93 * Push frame to all clients.
94 * @param {ArrayBuffer} f
95 * @private
96 */
97 llUsbGnubby.prototype.publishFrame_ = function(f) {
98 var old = this.clients;
99
100 var remaining = [];
101 var changes = false;
102 for (var i = 0; i < old.length; ++i) {
103 var client = old[i];
104 if (client.receivedFrame(f)) {
105 // Client still alive; keep on list.
106 remaining.push(client);
107 } else {
108 changes = true;
109 console.log(UTIL_fmt(
110 '[' + client.cid.toString(16) + '] left?'));
111 }
112 }
113 if (changes) this.clients = remaining;
114 };
115
116 /**
117 * @return {boolean} whether this device is open and ready to use.
118 * @private
119 */
120 llUsbGnubby.prototype.readyToUse_ = function() {
121 if (this.closing) return false;
122 if (!this.dev) return false;
123
124 return true;
125 };
126
127 /**
128 * Reads one reply from the low-level device.
129 * @private
130 */
131 llUsbGnubby.prototype.readOneReply_ = function() {
132 if (!this.readyToUse_()) return; // No point in continuing.
133 if (this.updating) return; // Do not bother waiting for final update reply.
134
135 var self = this;
136
137 function inTransferComplete(x) {
138 self.inTransferPending = false;
139
140 if (!self.readyToUse_()) return; // No point in continuing.
141
142 if (chrome.runtime.lastError) {
143 console.warn(UTIL_fmt('lastError: ' + chrome.runtime.lastError));
144 console.log(chrome.runtime.lastError);
145 window.setTimeout(function() { self.destroy(); }, 0);
146 return;
147 }
148
149 if (x.data) {
150 var u8 = new Uint8Array(x.data);
151 console.log(UTIL_fmt('<' + UTIL_BytesToHex(u8)));
152
153 self.publishFrame_(x.data);
154
155 // Write another pending request, if any.
156 window.setTimeout(
157 function() {
158 self.txqueue.shift(); // Drop sent frame from queue.
159 self.writeOneRequest_();
160 },
161 0);
162 } else {
163 console.log(UTIL_fmt('no x.data!'));
164 console.log(x);
165 window.setTimeout(function() { self.destroy(); }, 0);
166 }
167 }
168
169 if (this.inTransferPending == false) {
170 this.inTransferPending = true;
171 chrome.usb.bulkTransfer(
172 /** @type {!chrome.usb.ConnectionHandle} */(this.dev),
173 { direction: 'in', endpoint: this.inEndpoint, length: 2048 },
174 inTransferComplete);
175 } else {
176 throw 'inTransferPending!';
177 }
178 };
179
180 /**
181 * Register a client for this gnubby.
182 * @param {*} who The client.
183 */
184 llUsbGnubby.prototype.registerClient = function(who) {
185 for (var i = 0; i < this.clients.length; ++i) {
186 if (this.clients[i] === who) return; // Already registered.
187 }
188 this.clients.push(who);
189 };
190
191 /**
192 * De-register a client.
193 * @param {*} who The client.
194 * @return {number} The number of remaining listeners for this device, or -1
195 * Returns number of remaining listeners for this device.
196 * if this had no clients to start with.
197 */
198 llUsbGnubby.prototype.deregisterClient = function(who) {
199 var current = this.clients;
200 if (current.length == 0) return -1;
201 this.clients = [];
202 for (var i = 0; i < current.length; ++i) {
203 var client = current[i];
204 if (client !== who) this.clients.push(client);
205 }
206 return this.clients.length;
207 };
208
209 /**
210 * @param {*} who The client.
211 * @return {boolean} Whether this device has who as a client.
212 */
213 llUsbGnubby.prototype.hasClient = function(who) {
214 if (this.clients.length == 0) return false;
215 for (var i = 0; i < this.clients.length; ++i) {
216 if (who === this.clients[i])
217 return true;
218 }
219 return false;
220 };
221
222 /**
223 * Stuff queued frames from txqueue[] to device, one by one.
224 * @private
225 */
226 llUsbGnubby.prototype.writeOneRequest_ = function() {
227 if (!this.readyToUse_()) return; // No point in continuing.
228
229 if (this.txqueue.length == 0) return; // Nothing to send.
230
231 var frame = this.txqueue[0];
232
233 var self = this;
234 function OutTransferComplete(x) {
235 self.outTransferPending = false;
236
237 if (!self.readyToUse_()) return; // No point in continuing.
238
239 if (chrome.runtime.lastError) {
240 console.warn(UTIL_fmt('lastError: ' + chrome.runtime.lastError));
241 console.log(chrome.runtime.lastError);
242 window.setTimeout(function() { self.destroy(); }, 0);
243 return;
244 }
245
246 window.setTimeout(function() { self.readOneReply_(); }, 0);
247 };
248
249 var u8 = new Uint8Array(frame);
250 console.log(UTIL_fmt('>' + UTIL_BytesToHex(u8)));
251
252 if (this.outTransferPending == false) {
253 this.outTransferPending = true;
254 chrome.usb.bulkTransfer(
255 /** @type {!chrome.usb.ConnectionHandle} */(this.dev),
256 { direction: 'out', endpoint: this.outEndpoint, data: frame },
257 OutTransferComplete);
258 } else {
259 throw 'outTransferPending!';
260 }
261 };
262
263 /**
264 * Check whether channel is locked for this request or not.
265 * @param {number} cid
266 * @param {number} cmd
267 * @return {boolean} true if not locked for this request.
268 * @private
269 */
270 llUsbGnubby.prototype.checkLock_ = function(cid, cmd) {
271 if (this.lockCID) {
272 // We have an active lock.
273 if (this.lockCID != cid) {
274 // Some other channel has active lock.
275
276 if (cmd != llGnubby.CMD_SYNC) {
277 // Anything but SYNC gets an immediate busy.
278 var busy = new Uint8Array(
279 [(cid >> 24) & 255,
280 (cid >> 16) & 255,
281 (cid >> 8) & 255,
282 cid & 255,
283 llGnubby.CMD_ERROR,
284 0, 1, // length
285 llGnubby.BUSY]);
286 // Log the synthetic busy too.
287 console.log(UTIL_fmt('<' + UTIL_BytesToHex(busy)));
288 this.publishFrame_(busy.buffer);
289 return false;
290 }
291
292 // SYNC gets to go to the device to flush OS tx/rx queues.
293 // The usb firmware always responds to SYNC, regardless of lock status.
294 }
295 }
296 return true;
297 };
298
299 /**
300 * Update or grab lock.
301 * @param {number} cid
302 * @param {number} cmd
303 * @param {number} arg
304 * @private
305 */
306 llUsbGnubby.prototype.updateLock_ = function(cid, cmd, arg) {
307 if (this.lockCID == 0 || this.lockCID == cid) {
308 // It is this caller's or nobody's lock.
309 if (this.lockTID) {
310 window.clearTimeout(this.lockTID);
311 this.lockTID = null;
312 }
313
314 if (cmd == llGnubby.CMD_LOCK) {
315 var nseconds = arg;
316 if (nseconds != 0) {
317 this.lockCID = cid;
318 // Set tracking time to be .1 seconds longer than usb device does.
319 this.lockMillis = nseconds * 1000 + 100;
320 } else {
321 // Releasing lock voluntarily.
322 this.lockCID = 0;
323 }
324 }
325
326 // (re)set the lock timeout if we still hold it.
327 if (this.lockCID) {
328 var self = this;
329 this.lockTID = window.setTimeout(
330 function() {
331 console.warn(UTIL_fmt(
332 'lock for CID ' + cid.toString(16) + ' expired!'));
333 self.lockTID = null;
334 self.lockCID = 0;
335 },
336 this.lockMillis);
337 }
338 }
339 };
340
341 /**
342 * Queue command to be sent.
343 * If queue was empty, initiate the write.
344 * @param {number} cid The client's channel ID.
345 * @param {number} cmd The command to send.
346 * @param {ArrayBuffer} data
347 */
348 llUsbGnubby.prototype.queueCommand = function(cid, cmd, data) {
349 if (!this.dev) return;
350 if (!this.checkLock_(cid, cmd)) return;
351
352 var u8 = new Uint8Array(data);
353 var frame = new Uint8Array(u8.length + 7);
354
355 frame[0] = cid >>> 24;
356 frame[1] = cid >>> 16;
357 frame[2] = cid >>> 8;
358 frame[3] = cid;
359 frame[4] = cmd;
360 frame[5] = (u8.length >> 8);
361 frame[6] = (u8.length & 255);
362
363 frame.set(u8, 7);
364
365 var lockArg = (u8.length > 0) ? u8[0] : 0;
366 this.updateLock_(cid, cmd, lockArg);
367
368 var wasEmpty = (this.txqueue.length == 0);
369 this.txqueue.push(frame.buffer);
370 if (wasEmpty) this.writeOneRequest_();
371 };
372
373 /**
374 * @param {function(Array)} cb
375 */
376 llUsbGnubby.enumerate = function(cb) {
377 chrome.usb.getDevices({'vendorId': 4176, 'productId': 529}, cb);
378 };
379
380 /**
381 * @param {Gnubbies} gnubbies The gnubbies instances this device is enumerated
382 * in.
383 * @param {number} which The index of the device to open.
384 * @param {!chrome.usb.Device} dev The device to open.
385 * @param {function(number, llGnubby=)} cb Called back with the
386 * result of opening the device.
387 */
388 llUsbGnubby.open = function(gnubbies, which, dev, cb) {
389 /** @param {chrome.usb.ConnectionHandle=} handle */
390 function deviceOpened(handle) {
391 if (!handle) {
392 console.warn(UTIL_fmt('failed to open device. permissions issue?'));
393 cb(-llGnubby.NODEVICE);
394 return;
395 }
396 var nonNullHandle = /** @type {!chrome.usb.ConnectionHandle} */ (handle);
397 chrome.usb.listInterfaces(nonNullHandle, function(descriptors) {
398 var inEndpoint, outEndpoint;
399 for (var i = 0; i < descriptors.length; i++) {
400 var descriptor = descriptors[i];
401 for (var j = 0; j < descriptor.endpoints.length; j++) {
402 var endpoint = descriptor.endpoints[j];
403 if (inEndpoint == undefined && endpoint.type == 'bulk' &&
404 endpoint.direction == 'in') {
405 inEndpoint = endpoint.address;
406 }
407 if (outEndpoint == undefined && endpoint.type == 'bulk' &&
408 endpoint.direction == 'out') {
409 outEndpoint = endpoint.address;
410 }
411 }
412 }
413 if (inEndpoint == undefined || outEndpoint == undefined) {
414 console.warn(UTIL_fmt('device lacking an endpoint (broken?)'));
415 chrome.usb.closeDevice(nonNullHandle);
416 cb(-llGnubby.NODEVICE);
417 return;
418 }
419 // Try getting it claimed now.
420 chrome.usb.claimInterface(nonNullHandle, 0, function() {
421 if (chrome.runtime.lastError) {
422 console.warn(UTIL_fmt('lastError: ' + chrome.runtime.lastError));
423 console.log(chrome.runtime.lastError);
424 }
425 var claimed = !chrome.runtime.lastError;
426 if (!claimed) {
427 console.warn(UTIL_fmt('failed to claim interface. busy?'));
428 // Claim failed? Let the callers know and bail out.
429 chrome.usb.closeDevice(nonNullHandle);
430 cb(-llGnubby.BUSY);
431 return;
432 }
433 var gnubby = new llUsbGnubby(gnubbies, nonNullHandle, which, inEndpoint,
434 outEndpoint);
435 cb(-llGnubby.OK, gnubby);
436 });
437 });
438 }
439
440 if (llUsbGnubby.runningOnCrOS === undefined) {
441 llUsbGnubby.runningOnCrOS =
442 (window.navigator.appVersion.indexOf('; CrOS ') != -1);
443 }
444 if (llUsbGnubby.runningOnCrOS) {
445 chrome.usb.requestAccess(dev, 0, function(success) {
446 // Even though the argument to requestAccess is a chrome.usb.Device, the
447 // access request is for access to all devices with the same vid/pid.
448 // Curiously, if the first chrome.usb.requestAccess succeeds, a second
449 // call with a separate device with the same vid/pid fails. Since
450 // chrome.usb.openDevice will fail if a previous access request really
451 // failed, just ignore the outcome of the access request and move along.
452 chrome.usb.openDevice(dev, deviceOpened);
453 });
454 } else {
455 chrome.usb.openDevice(dev, deviceOpened);
456 }
457 };
458
459 /**
460 * @param {*} dev
461 * @return {llGnubbyDeviceId} A device identifier for the device.
462 */
463 llUsbGnubby.deviceToDeviceId = function(dev) {
464 var usbDev = /** @type {!chrome.usb.Device} */ (dev);
465 var deviceId = { namespace: llUsbGnubby.NAMESPACE, device: usbDev.device };
466 return deviceId;
467 };
468
469 /**
470 * Registers this implementation with gnubbies.
471 * @param {Gnubbies} gnubbies
472 */
473 llUsbGnubby.register = function(gnubbies) {
474 var USB_GNUBBY_IMPL = {
475 enumerate: llUsbGnubby.enumerate,
476 deviceToDeviceId: llUsbGnubby.deviceToDeviceId,
477 open: llUsbGnubby.open
478 };
479 gnubbies.registerNamespace(llUsbGnubby.NAMESPACE, USB_GNUBBY_IMPL);
480 };
OLDNEW
« no previous file with comments | « chrome/browser/resources/cryptotoken/llhidgnubby.js ('k') | chrome/browser/resources/cryptotoken/manifest.json » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698