15c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu// Copyright 2014 The Chromium Authors. All rights reserved.
25c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu// Use of this source code is governed by a BSD-style license that can be
35c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu// found in the LICENSE file.
45c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
55c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
65c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @fileoverview Implements a low-level gnubby driver based on chrome.hid.
75c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
85c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu'use strict';
95c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Low level gnubby 'driver'. One per physical USB device.
125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {Gnubbies} gnubbies The gnubbies instances this device is enumerated
135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *     in.
145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {!chrome.hid.ConnectionHandle} dev The device.
155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {number} id The device's id.
165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @constructor
175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @implements {llGnubby}
185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liufunction llHidGnubby(gnubbies, dev, id) {
205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {Gnubbies} */
215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.gnubbies_ = gnubbies;
225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.dev = dev;
235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.id = id;
245c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.txqueue = [];
255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.clients = [];
265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.lockCID = 0;     // channel ID of client holding a lock, if != 0.
275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.lockMillis = 0;  // current lock period.
285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.lockTID = null;  // timer id of lock timeout.
295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.closing = false;  // device to be closed by receive loop.
305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.updating = false;  // device firmware is in final stage of updating.
315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu}
325c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Namespace for the llHidGnubby implementation.
355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @const
365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiullHidGnubby.NAMESPACE = 'hid';
385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** Destroys this low-level device instance. */
405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiullHidGnubby.prototype.destroy = function() {
415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (!this.dev) return;  // Already dead.
425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
4346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  this.gnubbies_.removeOpenDevice(
4446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      {namespace: llHidGnubby.NAMESPACE, device: this.id});
455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.closing = true;
465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  console.log(UTIL_fmt('llHidGnubby.destroy()'));
485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // Synthesize a close error frame to alert all clients,
505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // some of which might be in read state.
515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  //
525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // Use magic CID 0 to address all.
535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.publishFrame_(new Uint8Array([
545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        0, 0, 0, 0,  // broadcast CID
555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        llGnubby.CMD_ERROR,
565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        0, 1,  // length
575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        llGnubby.GONE]).buffer);
585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // Set all clients to closed status and remove them.
605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  while (this.clients.length != 0) {
615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    var client = this.clients.shift();
625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (client) client.closed = true;
635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.lockTID) {
665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    window.clearTimeout(this.lockTID);
675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.lockTID = null;
685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var dev = this.dev;
715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.dev = null;
725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
7346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  chrome.hid.disconnect(dev.connectionId, function() {
745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    console.log(UTIL_fmt('Device ' + dev.handle + ' closed'));
7546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  });
765c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Push frame to all clients.
80010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {ArrayBuffer} f Data to push
815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiullHidGnubby.prototype.publishFrame_ = function(f) {
845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var old = this.clients;
855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var remaining = [];
875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var changes = false;
885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  for (var i = 0; i < old.length; ++i) {
895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    var client = old[i];
905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (client.receivedFrame(f)) {
915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // Client still alive; keep on list.
925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      remaining.push(client);
935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    } else {
945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      changes = true;
955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      console.log(UTIL_fmt(
965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          '[' + client.cid.toString(16) + '] left?'));
975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
985c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (changes) this.clients = remaining;
1005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
1015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
1035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Register a client for this gnubby.
1045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {*} who The client.
1055c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
1065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiullHidGnubby.prototype.registerClient = function(who) {
1075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  for (var i = 0; i < this.clients.length; ++i) {
1085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (this.clients[i] === who) return;  // Already registered.
1095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
1105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.clients.push(who);
1115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.clients.length == 1) {
1125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // First client? Kick off read loop.
1135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.readLoop_();
1145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
1155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
1165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
1185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * De-register a client.
1195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {*} who The client.
1205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {number} The number of remaining listeners for this device, or -1
1215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Returns number of remaining listeners for this device.
1225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *     if this had no clients to start with.
1235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
1245c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiullHidGnubby.prototype.deregisterClient = function(who) {
1255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var current = this.clients;
1265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (current.length == 0) return -1;
1275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.clients = [];
1285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  for (var i = 0; i < current.length; ++i) {
1295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    var client = current[i];
1305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (client !== who) this.clients.push(client);
1315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
1325c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  return this.clients.length;
1335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
1345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
1365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {*} who The client.
1375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {boolean} Whether this device has who as a client.
1385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
1395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiullHidGnubby.prototype.hasClient = function(who) {
1405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.clients.length == 0) return false;
1415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  for (var i = 0; i < this.clients.length; ++i) {
1425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (who === this.clients[i])
1435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      return true;
1445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
1455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  return false;
1465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
1475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
1495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Reads all incoming frames and notifies clients of their receipt.
1505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
1515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
1525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiullHidGnubby.prototype.readLoop_ = function() {
1535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  //console.log(UTIL_fmt('entering readLoop'));
1545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (!this.dev) return;
1555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.closing) {
1575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.destroy();
1585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return;
1595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
1605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // No interested listeners, yet we hit readLoop().
1625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // Must be clean-up. We do this here to make sure no transfer is pending.
1635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (!this.clients.length) {
1645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.closing = true;
1655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.destroy();
1665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return;
1675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
1685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // firmwareUpdate() sets this.updating when writing the last block before
1705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // the signature. We process that reply with the already pending
1715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // read transfer but we do not want to start another read transfer for the
1725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // signature block, since that request will have no reply.
1735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // Instead we will see the device drop and re-appear on the bus.
1745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // Current libusb on some platforms gets unhappy when transfer are pending
1755c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // when that happens.
176010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  // TODO: revisit once Chrome stabilizes its behavior.
1775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.updating) {
1785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    console.log(UTIL_fmt('device updating. Ending readLoop()'));
1795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return;
1805c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
1815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var self = this;
1835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  chrome.hid.receive(
1845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.dev.connectionId,
1855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    64,
1865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    function(x) {
1875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      if (chrome.runtime.lastError || !x) {
1885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        console.log(UTIL_fmt('got lastError'));
1895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        console.log(chrome.runtime.lastError);
1905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        window.setTimeout(function() { self.destroy(); }, 0);
1915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        return;
1925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      }
1935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      var u8 = new Uint8Array(x);
1945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      //console.log(UTIL_fmt('<' + UTIL_BytesToHex(u8)));
1955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      self.publishFrame_(x);
1975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1985c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // Read more.
1995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      window.setTimeout(function() { self.readLoop_(); }, 0);
2005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
2015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  );
2025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
2035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
2055c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Check whether channel is locked for this request or not.
206010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {number} cid Channel id
207010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {number} cmd Request command
2085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {boolean} true if not locked for this request.
2095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
2105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
2115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiullHidGnubby.prototype.checkLock_ = function(cid, cmd) {
2125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.lockCID) {
2135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // We have an active lock.
2145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (this.lockCID != cid) {
2155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // Some other channel has active lock.
2165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      if (cmd != llGnubby.CMD_SYNC) {
2185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        // Anything but SYNC gets an immediate busy.
2195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        var busy = new Uint8Array(
2205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu            [(cid >> 24) & 255,
2215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu             (cid >> 16) & 255,
2225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu             (cid >> 8) & 255,
2235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu             cid & 255,
2245c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu             llGnubby.CMD_ERROR,
2255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu             0, 1,  // length
2265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu             llGnubby.BUSY]);
2275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        // Log the synthetic busy too.
2285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        console.log(UTIL_fmt('<' + UTIL_BytesToHex(busy)));
2295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.publishFrame_(busy.buffer);
2305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        return false;
2315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      }
2325c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // SYNC gets to go to the device to flush OS tx/rx queues.
2345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // The usb firmware always responds to SYNC, regardless of lock status.
2355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
2365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
2375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  return true;
2385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
2395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
2415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Update or grab lock.
242010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {number} cid Channel ID
243010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {number} cmd Command
244010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {number} arg Command argument
2455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
2465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
2475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiullHidGnubby.prototype.updateLock_ = function(cid, cmd, arg) {
2485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.lockCID == 0 || this.lockCID == cid) {
2495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // It is this caller's or nobody's lock.
2505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (this.lockTID) {
2515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      window.clearTimeout(this.lockTID);
2525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.lockTID = null;
2535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
2545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (cmd == llGnubby.CMD_LOCK) {
2565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      var nseconds = arg;
2575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      if (nseconds != 0) {
2585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.lockCID = cid;
2595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        // Set tracking time to be .1 seconds longer than usb device does.
2605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.lockMillis = nseconds * 1000 + 100;
2615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      } else {
2625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        // Releasing lock voluntarily.
2635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.lockCID = 0;
2645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      }
2655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
2665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // (re)set the lock timeout if we still hold it.
2685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (this.lockCID) {
2695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      var self = this;
2705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.lockTID = window.setTimeout(
2715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          function() {
2725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu            console.warn(UTIL_fmt(
2735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu                'lock for CID ' + cid.toString(16) + ' expired!'));
2745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu            self.lockTID = null;
2755c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu            self.lockCID = 0;
2765c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          },
2775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          this.lockMillis);
2785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
2795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
2805c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
2815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
2835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Queue command to be sent.
2845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * If queue was empty, initiate the write.
2855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {number} cid The client's channel ID.
2865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {number} cmd The command to send.
28746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles) * @param {ArrayBuffer|Uint8Array} data Command arguments
2885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
2895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiullHidGnubby.prototype.queueCommand = function(cid, cmd, data) {
2905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (!this.dev) return;
2915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (!this.checkLock_(cid, cmd)) return;
2925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var u8 = new Uint8Array(data);
2945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var f = new Uint8Array(64);
2955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  llHidGnubby.setCid_(f, cid);
2975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  f[4] = cmd;
2985c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  f[5] = (u8.length >> 8);
2995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  f[6] = (u8.length & 255);
3005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var lockArg = (u8.length > 0) ? u8[0] : 0;
3025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // Fragment over our 64 byte frames.
3045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var n = 7;
3055c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var seq = 0;
3065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  for (var i = 0; i < u8.length; ++i) {
3075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    f[n++] = u8[i];
3085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (n == f.length) {
3095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.queueFrame_(f.buffer, cid, cmd, lockArg);
3105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      f = new Uint8Array(64);
3125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      llHidGnubby.setCid_(f, cid);
3135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      cmd = f[4] = seq++;
3145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      n = 5;
3155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
3165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
3175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (n != 5) {
3185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.queueFrame_(f.buffer, cid, cmd, lockArg);
3195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
3205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
3215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
3235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Sets the channel id in the frame.
324010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {Uint8Array} frame Data frame
3255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {number} cid The client's channel ID.
3265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
3275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
3285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiullHidGnubby.setCid_ = function(frame, cid) {
3295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  frame[0] = cid >>> 24;
3305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  frame[1] = cid >>> 16;
3315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  frame[2] = cid >>> 8;
3325c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  frame[3] = cid;
3335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
3345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
3365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Updates the lock, and queues the frame for sending. Also begins sending if
3375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * no other writes are outstanding.
338010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {ArrayBuffer} frame Data frame
3395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {number} cid The client's channel ID.
3405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {number} cmd The command to send.
341010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {number} arg Command argument
3425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
3435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
3445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiullHidGnubby.prototype.queueFrame_ = function(frame, cid, cmd, arg) {
3455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.updateLock_(cid, cmd, arg);
3465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var wasEmpty = (this.txqueue.length == 0);
3475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.txqueue.push(frame);
3485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (wasEmpty) this.writePump_();
3495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
3505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
3525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Stuff queued frames from txqueue[] to device, one by one.
3535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
3545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
3555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiullHidGnubby.prototype.writePump_ = function() {
3565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (!this.dev) return;  // Ignore.
3575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.txqueue.length == 0) return;  // Done with current queue.
3595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var frame = this.txqueue[0];
3615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var self = this;
3635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  function transferComplete(x) {
3645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (chrome.runtime.lastError) {
3655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      console.log(UTIL_fmt('got lastError'));
3665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      console.log(chrome.runtime.lastError);
3675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      window.setTimeout(function() { self.destroy(); }, 0);
3685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      return;
3695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
3705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    self.txqueue.shift();  // drop sent frame from queue.
3715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (self.txqueue.length != 0) {
3725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      window.setTimeout(function() { self.writePump_(); }, 0);
3735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
3745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  };
3755c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3765c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var u8 = new Uint8Array(frame);
3775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  //console.log(UTIL_fmt('>' + UTIL_BytesToHex(u8)));
3785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var u8f = new Uint8Array(64);
3805c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  for (var i = 0; i < u8.length; ++i) {
3815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    u8f[i] = u8[i];
3825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
3835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  chrome.hid.send(
3855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.dev.connectionId,
3865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      0, // report Id
3875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      u8f.buffer,
3885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      transferComplete
3895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  );
3905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
3915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
392010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {function(Array)} cb Enumeration callback
3935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
3945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiullHidGnubby.enumerate = function(cb) {
3955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  chrome.hid.getDevices({'vendorId': 4176, 'productId': 512}, cb);
3965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
3975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3985c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
3995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {Gnubbies} gnubbies The gnubbies instances this device is enumerated
4005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *     in.
4015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {number} which The index of the device to open.
4025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {!chrome.hid.HidDeviceInfo} dev The device to open.
4035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {function(number, llGnubby=)} cb Called back with the
4045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *     result of opening the device.
4055c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
4065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiullHidGnubby.open = function(gnubbies, which, dev, cb) {
4075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  chrome.hid.connect(dev.deviceId, function(handle) {
4085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (chrome.runtime.lastError) {
4095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      console.log(chrome.runtime.lastError);
4105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
4115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (!handle) {
4125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      console.warn(UTIL_fmt('failed to connect device. permissions issue?'));
4135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      cb(-llGnubby.NODEVICE);
4145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      return;
4155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
4165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    var nonNullHandle = /** @type {!chrome.hid.HidConnection} */ (handle);
4175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    var gnubby = new llHidGnubby(gnubbies, nonNullHandle, which);
4185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    cb(-llGnubby.OK, gnubby);
4195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  });
4205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
4215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
4225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
423010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {*} dev A browser API device object
4245c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {llGnubbyDeviceId} A device identifier for the device.
4255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
4265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiullHidGnubby.deviceToDeviceId = function(dev) {
4275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var hidDev = /** @type {!chrome.hid.HidDeviceInfo} */ (dev);
4285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var deviceId = { namespace: llHidGnubby.NAMESPACE, device: hidDev.deviceId };
4295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  return deviceId;
4305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
4315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
4325c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
4335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Registers this implementation with gnubbies.
434010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {Gnubbies} gnubbies Gnubbies registry
4355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
4365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiullHidGnubby.register = function(gnubbies) {
4375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var HID_GNUBBY_IMPL = {
4385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    enumerate: llHidGnubby.enumerate,
4395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    deviceToDeviceId: llHidGnubby.deviceToDeviceId,
4405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    open: llHidGnubby.open
4415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  };
4425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  gnubbies.registerNamespace(llHidGnubby.NAMESPACE, HID_GNUBBY_IMPL);
4435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
444