Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / cryptotoken / gnubbies.js
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
3 // found in the LICENSE file.
4
5 /**
6  * @fileoverview A class for managing all enumerated gnubby devices.
7  */
8 'use strict';
9
10 /**
11  * @typedef {{
12  *   namespace: string,
13  *   device: number
14  * }}
15  */
16 var llGnubbyDeviceId;
17
18 /**
19  * @typedef {{
20  *   enumerate: function(function(Array)),
21  *   deviceToDeviceId: function(*): llGnubbyDeviceId,
22  *   open: function(Gnubbies, number, *, function(number, llGnubby=))
23  * }}
24  */
25 var GnubbyNamespaceImpl;
26
27 /**
28  * Manager of opened devices.
29  * @constructor
30  */
31 function Gnubbies() {
32   /** @private {Object.<string, Array>} */
33   this.devs_ = {};
34   this.pendingEnumerate = [];  // clients awaiting an enumerate
35   /** @private {Object.<string, GnubbyNamespaceImpl>} */
36   this.impl_ = {};
37   /** @private {Object.<string, Object.<number, !llGnubby>>} */
38   this.openDevs_ = {};
39   /** @private {Object.<string, Object.<number, *>>} */
40   this.pendingOpens_ = {};  // clients awaiting an open
41 }
42
43 /**
44  * Registers a new gnubby namespace, i.e. an implementation of the
45  * enumerate/open functions for all devices within a namespace.
46  * @param {string} namespace The namespace of the numerator, e.g. 'usb'.
47  * @param {GnubbyNamespaceImpl} impl The implementation.
48  */
49 Gnubbies.prototype.registerNamespace = function(namespace, impl) {
50   this.impl_[namespace] = impl;
51 };
52
53 /**
54  * @param {llGnubbyDeviceId} which The device to remove.
55  */
56 Gnubbies.prototype.removeOpenDevice = function(which) {
57   if (this.openDevs_[which.namespace] &&
58       this.openDevs_[which.namespace].hasOwnProperty(which.device)) {
59     delete this.openDevs_[which.namespace][which.device];
60   }
61 };
62
63 /** Close all enumerated devices. */
64 Gnubbies.prototype.closeAll = function() {
65   if (this.inactivityTimer) {
66     this.inactivityTimer.clearTimeout();
67     this.inactivityTimer = undefined;
68   }
69   // Close and stop talking to any gnubbies we have enumerated.
70   for (var namespace in this.openDevs_) {
71     for (var dev in this.openDevs_[namespace]) {
72       var deviceId = Number(dev);
73       this.openDevs_[namespace][deviceId].destroy();
74     }
75   }
76   this.devs_ = {};
77   this.openDevs_ = {};
78 };
79
80 /**
81  * @param {function(number, Array.<llGnubbyDeviceId>)} cb Called back with the
82  *     result of enumerating.
83  */
84 Gnubbies.prototype.enumerate = function(cb) {
85   var self = this;
86
87   /**
88    * @param {string} namespace The namespace that was enumerated.
89    * @param {boolean} lastNamespace Whether this was the last namespace.
90    * @param {Array.<llGnubbyDeviceId>} existingDeviceIds Previously enumerated
91    *     device IDs (from other namespaces), if any.
92    * @param {Array} devs The devices in the namespace.
93    */
94   function enumerated(namespace, lastNamespace, existingDeviceIds, devs) {
95     if (chrome.runtime.lastError) {
96       console.warn(UTIL_fmt('lastError: ' + chrome.runtime.lastError));
97       console.log(chrome.runtime.lastError);
98       devs = [];
99     }
100
101     console.log(UTIL_fmt('Enumerated ' + devs.length + ' gnubbies'));
102     console.log(devs);
103
104     var presentDevs = {};
105     var deviceIds = [];
106     var deviceToDeviceId = self.impl_[namespace].deviceToDeviceId;
107     for (var i = 0; i < devs.length; ++i) {
108       var deviceId = deviceToDeviceId(devs[i]);
109       deviceIds.push(deviceId);
110       presentDevs[deviceId.device] = devs[i];
111     }
112
113     var toRemove = [];
114     for (var dev in self.openDevs_[namespace]) {
115       if (!presentDevs.hasOwnProperty(dev)) {
116         toRemove.push(dev);
117       }
118     }
119
120     for (var i = 0; i < toRemove.length; i++) {
121       dev = toRemove[i];
122       if (self.openDevs_[namespace][dev]) {
123         self.openDevs_[namespace][dev].destroy();
124         delete self.openDevs_[namespace][dev];
125       }
126     }
127
128     self.devs_[namespace] = devs;
129     existingDeviceIds.push.apply(existingDeviceIds, deviceIds);
130     if (lastNamespace) {
131       while (self.pendingEnumerate.length != 0) {
132         var cb = self.pendingEnumerate.shift();
133         cb(-llGnubby.OK, existingDeviceIds);
134       }
135     }
136   }
137
138   this.pendingEnumerate.push(cb);
139   if (this.pendingEnumerate.length == 1) {
140     var namespaces = Object.keys(/** @type {!Object} */ (this.impl_));
141     if (!namespaces.length) {
142       cb(-llGnubby.OK, []);
143       return;
144     }
145     var deviceIds = [];
146     for (var i = 0; i < namespaces.length; i++) {
147       var namespace = namespaces[i];
148       var enumerator = this.impl_[namespace].enumerate;
149       enumerator(
150           enumerated.bind(null,
151                           namespace,
152                           i == namespaces.length - 1,
153                           deviceIds));
154     }
155   }
156 };
157
158 /**
159  * Amount of time past last activity to set the inactivity timer to, in millis.
160  * @const
161  */
162 Gnubbies.INACTIVITY_TIMEOUT_MARGIN_MILLIS = 30000;
163
164 /**
165  * @param {number|undefined} opt_timeoutMillis Timeout in milliseconds
166  */
167 Gnubbies.prototype.resetInactivityTimer = function(opt_timeoutMillis) {
168   var millis = opt_timeoutMillis ?
169       opt_timeoutMillis + Gnubbies.INACTIVITY_TIMEOUT_MARGIN_MILLIS :
170       Gnubbies.INACTIVITY_TIMEOUT_MARGIN_MILLIS;
171   if (!this.inactivityTimer) {
172     this.inactivityTimer =
173         new CountdownTimer(millis, this.inactivityTimeout_.bind(this));
174   } else if (millis > this.inactivityTimer.millisecondsUntilExpired()) {
175     this.inactivityTimer.clearTimeout();
176     this.inactivityTimer.setTimeout(millis, this.inactivityTimeout_.bind(this));
177   }
178 };
179
180 /**
181  * Called when the inactivity timeout expires.
182  * @private
183  */
184 Gnubbies.prototype.inactivityTimeout_ = function() {
185   this.inactivityTimer = undefined;
186   for (var namespace in this.openDevs_) {
187     for (var dev in this.openDevs_[namespace]) {
188       var deviceId = Number(dev);
189       console.warn(namespace + ' device ' + deviceId +
190           ' still open after inactivity, closing');
191       this.openDevs_[namespace][deviceId].destroy();
192       this.removeOpenDevice({namespace: namespace, device: deviceId});
193     }
194   }
195 };
196
197 /**
198  * Opens and adds a new client of the specified device.
199  * @param {llGnubbyDeviceId} which Which device to open.
200  * @param {*} who Client of the device.
201  * @param {function(number, llGnubby=)} cb Called back with the result of
202  *     opening the device.
203  */
204 Gnubbies.prototype.addClient = function(which, who, cb) {
205   this.resetInactivityTimer();
206
207   var self = this;
208
209   function opened(gnubby, who, cb) {
210     if (gnubby.closing) {
211       // Device is closing or already closed.
212       self.removeClient(gnubby, who);
213       if (cb) { cb(-llGnubby.GONE); }
214     } else {
215       gnubby.registerClient(who);
216       if (cb) { cb(-llGnubby.OK, gnubby); }
217     }
218   }
219
220   function notifyOpenResult(rc) {
221     if (self.pendingOpens_[which.namespace]) {
222       while (self.pendingOpens_[which.namespace][which.device].length != 0) {
223         var client = self.pendingOpens_[which.namespace][which.device].shift();
224         client.cb(rc);
225       }
226       delete self.pendingOpens_[which.namespace][which.device];
227     }
228   }
229
230   var dev = null;
231   var deviceToDeviceId = this.impl_[which.namespace].deviceToDeviceId;
232   if (this.devs_[which.namespace]) {
233     for (var i = 0; i < this.devs_[which.namespace].length; i++) {
234       var device = this.devs_[which.namespace][i];
235       if (deviceToDeviceId(device).device == which.device) {
236         dev = device;
237         break;
238       }
239     }
240   }
241   if (!dev) {
242     // Index out of bounds. Device does not exist in current enumeration.
243     this.removeClient(null, who);
244     if (cb) { cb(-llGnubby.NODEVICE); }
245     return;
246   }
247
248   function openCb(rc, opt_gnubby) {
249     if (rc) {
250       notifyOpenResult(rc);
251       return;
252     }
253     if (!opt_gnubby) {
254       notifyOpenResult(-llGnubby.NODEVICE);
255       return;
256     }
257     var gnubby = /** @type {!llGnubby} */ (opt_gnubby);
258     if (!self.openDevs_[which.namespace]) {
259       self.openDevs_[which.namespace] = {};
260     }
261     self.openDevs_[which.namespace][which.device] = gnubby;
262     while (self.pendingOpens_[which.namespace][which.device].length != 0) {
263       var client = self.pendingOpens_[which.namespace][which.device].shift();
264       opened(gnubby, client.who, client.cb);
265     }
266     delete self.pendingOpens_[which.namespace][which.device];
267   }
268
269   if (this.openDevs_[which.namespace] &&
270       this.openDevs_[which.namespace].hasOwnProperty(which.device)) {
271     var gnubby = this.openDevs_[which.namespace][which.device];
272     opened(gnubby, who, cb);
273   } else {
274     var opener = {who: who, cb: cb};
275     if (!this.pendingOpens_.hasOwnProperty(which.namespace)) {
276       this.pendingOpens_[which.namespace] = {};
277     }
278     if (this.pendingOpens_[which.namespace].hasOwnProperty(which)) {
279       this.pendingOpens_[which.namespace][which.device].push(opener);
280     } else {
281       this.pendingOpens_[which.namespace][which.device] = [opener];
282       var openImpl = this.impl_[which.namespace].open;
283       openImpl(this, which.device, dev, openCb);
284     }
285   }
286 };
287
288 /**
289  * Removes a client from a low-level gnubby.
290  * @param {llGnubby} whichDev The gnubby.
291  * @param {*} who The client.
292  */
293 Gnubbies.prototype.removeClient = function(whichDev, who) {
294   console.log(UTIL_fmt('Gnubbies.removeClient()'));
295
296   this.resetInactivityTimer();
297
298   // De-register client from all known devices.
299   for (var namespace in this.openDevs_) {
300     for (var devId in this.openDevs_[namespace]) {
301       var deviceId = Number(devId);
302       var dev = this.openDevs_[namespace][deviceId];
303       if (dev.hasClient(who)) {
304         if (whichDev && dev != whichDev) {
305           console.warn('usbGnubby attached to more than one device!?');
306         }
307         if (!dev.deregisterClient(who)) {
308           dev.destroy();
309         }
310       }
311     }
312   }
313 };