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 A single gnubby signer wraps the process of opening a gnubby,
75c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * signing each challenge in an array of challenges until a success condition
85c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * is satisfied, and finally yielding the gnubby upon success.
96e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) *
105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu'use strict';
135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
15116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch * @typedef {{
16116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch *   code: number,
176e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) *   gnubby: (Gnubby|undefined),
18116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch *   challenge: (SignHelperChallenge|undefined),
19116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch *   info: (ArrayBuffer|undefined)
20116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch * }}
21116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch */
22116680a4aac90f2aa7413d9095a592090648e557Ben Murdochvar SingleSignerResult;
23116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
24116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch/**
255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Creates a new sign handler with a gnubby. This handler will perform a sign
265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * operation using each challenge in an array of challenges until its success
275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * condition is satisified, or an error or timeout occurs. The success condition
285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * is defined differently depending whether this signer is used for enrolling
295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * or for signing:
305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *
315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * For enroll, success is defined as each challenge yielding wrong data. This
325c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * means this gnubby is not currently enrolled for any of the appIds in any
335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * challenge.
345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *
35116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch * For sign, success is defined as any challenge yielding ok.
365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *
376e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * The complete callback is called only when the signer reaches success or
386e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * failure, i.e.  when there is no need for this signer to continue trying new
396e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * challenges.
405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *
416e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * @param {GnubbyDeviceId} gnubbyId Which gnubby to open.
425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {boolean} forEnroll Whether this signer is signing for an attempted
435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *     enroll operation.
44116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch * @param {function(SingleSignerResult)}
45116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch *     completeCb Called when this signer completes, i.e. no further results are
46116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch *     possible.
47116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch * @param {Countdown} timer An advisory timer, beyond whose expiration the
485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *     signer will not attempt any new operations, assuming the caller is no
495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu *     longer interested in the outcome.
505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {string=} opt_logMsgUrl A URL to post log messages to.
515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @constructor
525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
536e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)function SingleGnubbySigner(gnubbyId, forEnroll, completeCb, timer,
54116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    opt_logMsgUrl) {
556e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)  /** @private {GnubbyDeviceId} */
56116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  this.gnubbyId_ = gnubbyId;
575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {SingleGnubbySigner.State} */
585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.state_ = SingleGnubbySigner.State.INIT;
595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {boolean} */
605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.forEnroll_ = forEnroll;
61116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  /** @private {function(SingleSignerResult)} */
62116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  this.completeCb_ = completeCb;
63116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  /** @private {Countdown} */
64116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  this.timer_ = timer;
655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {string|undefined} */
665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.logMsgUrl_ = opt_logMsgUrl;
675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {!Array.<!SignHelperChallenge>} */
695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.challenges_ = [];
705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {number} */
715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.challengeIndex_ = 0;
725c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** @private {boolean} */
73116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  this.challengesSet_ = false;
745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
751320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  /** @private {!Object.<string, number>} */
761320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  this.cachedError_ = [];
775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu}
785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/** @enum {number} */
805c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSingleGnubbySigner.State = {
815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** Initial state. */
825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  INIT: 0,
835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** The signer is attempting to open a gnubby. */
845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  OPENING: 1,
855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** The signer's gnubby opened, but is busy. */
865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  BUSY: 2,
875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** The signer has an open gnubby, but no challenges to sign. */
885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  IDLE: 3,
895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** The signer is currently signing a challenge. */
905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  SIGNING: 4,
91116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  /** The signer got a final outcome. */
92116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  COMPLETE: 5,
935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** The signer is closing its gnubby. */
94116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  CLOSING: 6,
955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  /** The signer is closed. */
96116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  CLOSED: 7
97116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch};
98116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
99116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch/**
1006e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * @return {GnubbyDeviceId} This device id of the gnubby for this signer.
101116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch */
102116680a4aac90f2aa7413d9095a592090648e557Ben MurdochSingleGnubbySigner.prototype.getDeviceId = function() {
103116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  return this.gnubbyId_;
1045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
1055c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
1075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Attempts to open this signer's gnubby, if it's not already open.
1085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * (This is implicitly done by addChallenges.)
1095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
1105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSingleGnubbySigner.prototype.open = function() {
1115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.state_ == SingleGnubbySigner.State.INIT) {
1125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.state_ = SingleGnubbySigner.State.OPENING;
1136e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)    DEVICE_FACTORY_REGISTRY.getGnubbyFactory().openGnubby(
1146e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        this.gnubbyId_,
1156e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        this.forEnroll_,
1166e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        this.openCallback_.bind(this),
1176e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)        this.logMsgUrl_);
1185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
1195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
1205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
1225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Closes this signer's gnubby, if it's held.
1235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
1245c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSingleGnubbySigner.prototype.close = function() {
1255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (!this.gnubby_) return;
1265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.state_ = SingleGnubbySigner.State.CLOSING;
1275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.gnubby_.closeWhenIdle(this.closed_.bind(this));
1285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
1295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1305c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
1315c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Called when this signer's gnubby is closed.
1325c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
1335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
1345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSingleGnubbySigner.prototype.closed_ = function() {
1355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.gnubby_ = null;
1365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.state_ = SingleGnubbySigner.State.CLOSED;
1375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
1385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
140116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch * Begins signing the given challenges.
141116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch * @param {Array.<SignHelperChallenge>} challenges The challenges to sign.
1425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @return {boolean} Whether the challenges were accepted.
1435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
144116680a4aac90f2aa7413d9095a592090648e557Ben MurdochSingleGnubbySigner.prototype.doSign = function(challenges) {
145116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  if (this.challengesSet_) {
146116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    // Can't add new challenges once they've been set.
1475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return false;
1485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
1495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (challenges) {
1515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    console.log(this.gnubby_);
1525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    console.log(UTIL_fmt('adding ' + challenges.length + ' challenges'));
1535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    for (var i = 0; i < challenges.length; i++) {
1545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.challenges_.push(challenges[i]);
1555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
1565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
157116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  this.challengesSet_ = true;
1585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  switch (this.state_) {
1605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    case SingleGnubbySigner.State.INIT:
1615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.open();
1625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      break;
1635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    case SingleGnubbySigner.State.OPENING:
164116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      // The open has already commenced, so accept the challenges, but don't do
165116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      // anything.
1665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      break;
1675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    case SingleGnubbySigner.State.IDLE:
1685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      if (this.challengeIndex_ < challenges.length) {
169116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch        // Challenges set: start signing.
1705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.doSign_(this.challengeIndex_);
171116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      } else {
172116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch        // An empty list of challenges can be set during enroll, when the user
173116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch        // has no existing enrolled gnubbies. It's unexpected during sign, but
174116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch        // returning WRONG_DATA satisfies the caller in either case.
1755c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        var self = this;
1765c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        window.setTimeout(function() {
1775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          self.goToError_(DeviceStatusCodes.WRONG_DATA_STATUS);
1785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        }, 0);
1795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      }
1805c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      break;
1815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    case SingleGnubbySigner.State.SIGNING:
1825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // Already signing, so don't kick off a new sign, but accept the added
1835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // challenges.
1845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      break;
1855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    default:
1865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      return false;
1875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
1885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  return true;
1895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
1905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
1925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * How long to delay retrying a failed open.
1935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
1945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSingleGnubbySigner.OPEN_DELAY_MILLIS = 200;
1955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
1965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
197116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch * How long to delay retrying a sign requiring touch.
198116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch */
199116680a4aac90f2aa7413d9095a592090648e557Ben MurdochSingleGnubbySigner.SIGN_DELAY_MILLIS = 200;
200116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
201116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch/**
2025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {number} rc The result of the open operation.
2036e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * @param {Gnubby=} gnubby The opened gnubby, if open was successful (or busy).
2045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
2055c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
2065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSingleGnubbySigner.prototype.openCallback_ = function(rc, gnubby) {
2075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.state_ != SingleGnubbySigner.State.OPENING &&
2085c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.state_ != SingleGnubbySigner.State.BUSY) {
2095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // Open completed after close, perhaps? Ignore.
2105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return;
2115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
2125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  switch (rc) {
2145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    case DeviceStatusCodes.OK_STATUS:
2155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      if (!gnubby) {
2165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        console.warn(UTIL_fmt('open succeeded but gnubby is null, WTF?'));
2175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      } else {
2185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.gnubby_ = gnubby;
2195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.gnubby_.version(this.versionCallback_.bind(this));
2205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      }
2215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      break;
2225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    case DeviceStatusCodes.BUSY_STATUS:
2235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.gnubby_ = gnubby;
2245c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.state_ = SingleGnubbySigner.State.BUSY;
2255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // If there's still time, retry the open.
2265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      if (!this.timer_ || !this.timer_.expired()) {
2275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        var self = this;
2285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        window.setTimeout(function() {
2295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          if (self.gnubby_) {
2306e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)            DEVICE_FACTORY_REGISTRY.getGnubbyFactory().openGnubby(
2316e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                self.gnubbyId_,
2326e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                self.forEnroll_,
2336e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                self.openCallback_.bind(self),
2346e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)                self.logMsgUrl_);
2355c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu          }
2365c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        }, SingleGnubbySigner.OPEN_DELAY_MILLIS);
2375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      } else {
2385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.goToError_(DeviceStatusCodes.BUSY_STATUS);
2395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      }
2405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      break;
2415c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    default:
242010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)      // TODO: This won't be confused with success, but should it be
2435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // part of the same namespace as the other error codes, which are
2445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      // always in DeviceStatusCodes.*?
2451320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      this.goToError_(rc, true);
2465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
2475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
2485c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
2505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Called with the result of a version command.
2515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {number} rc Result of version command.
2525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {ArrayBuffer=} opt_data Version.
2535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
2545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
2555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSingleGnubbySigner.prototype.versionCallback_ = function(rc, opt_data) {
2565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (rc) {
2571320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    this.goToError_(rc, true);
2585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return;
2595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
2605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.state_ = SingleGnubbySigner.State.IDLE;
2615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.version_ = UTIL_BytesToString(new Uint8Array(opt_data || []));
2625c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.doSign_(this.challengeIndex_);
2635c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
2645c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
266010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {number} challengeIndex Index of challenge to sign
2675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
2685c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
2695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSingleGnubbySigner.prototype.doSign_ = function(challengeIndex) {
270116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  if (!this.gnubby_) {
271116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    // Already closed? Nothing to do.
272116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    return;
273116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  }
2745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.timer_ && this.timer_.expired()) {
275116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    // If the timer is expired, that means we never got a success response.
276116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    // We could have gotten wrong data on a partial set of challenges, but this
277116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    // means we don't yet know the final outcome. In any event, we don't yet
278116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    // know the final outcome: return timeout.
279116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    this.goToError_(DeviceStatusCodes.TIMEOUT_STATUS);
280116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    return;
281116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  }
282116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  if (!this.challengesSet_) {
283116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    this.state_ = SingleGnubbySigner.State.IDLE;
2845c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return;
2855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
2865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.state_ = SingleGnubbySigner.State.SIGNING;
2885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (challengeIndex >= this.challenges_.length) {
2905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.signCallback_(challengeIndex, DeviceStatusCodes.WRONG_DATA_STATUS);
2915c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return;
2925c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
2935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
2945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var challenge = this.challenges_[challengeIndex];
2955c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var challengeHash = challenge.challengeHash;
2965c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var appIdHash = challenge.appIdHash;
2975c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  var keyHandle = challenge.keyHandle;
2981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  if (this.cachedError_.hasOwnProperty(keyHandle)) {
2995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // Cache hit: return wrong data again.
3001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    this.signCallback_(challengeIndex, this.cachedError_[keyHandle]);
3015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  } else if (challenge.version && challenge.version != this.version_) {
3025c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // Sign challenge for a different version of gnubby: return wrong data.
3035c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.signCallback_(challengeIndex, DeviceStatusCodes.WRONG_DATA_STATUS);
3045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  } else {
305116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    var nowink = false;
3065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    this.gnubby_.sign(challengeHash, appIdHash, keyHandle,
3075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.signCallback_.bind(this, challengeIndex),
308010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)        nowink);
3095c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
3105c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
3115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
3135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Called with the result of a single sign operation.
3145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {number} challengeIndex the index of the challenge just attempted
3155c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @param {number} code the result of the sign operation
316010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {ArrayBuffer=} opt_info Optional result data
3175c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
3185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
3195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSingleGnubbySigner.prototype.signCallback_ =
3205c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    function(challengeIndex, code, opt_info) {
321116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  console.log(UTIL_fmt('gnubby ' + JSON.stringify(this.gnubbyId_) +
3225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      ', challenge ' + challengeIndex + ' yielded ' + code.toString(16)));
3235c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (this.state_ != SingleGnubbySigner.State.SIGNING) {
3245c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    console.log(UTIL_fmt('already done!'));
3255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    // We're done, the caller's no longer interested.
3265c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    return;
3275c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
3285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  // Cache wrong data or wrong length results, re-asking the gnubby to sign it
3301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  // won't produce different results.
3311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  if (code == DeviceStatusCodes.WRONG_DATA_STATUS ||
3321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      code == DeviceStatusCodes.WRONG_LENGTH_STATUS) {
3335c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (challengeIndex < this.challenges_.length) {
3345c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      var challenge = this.challenges_[challengeIndex];
3351320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      if (!this.cachedError_.hasOwnProperty(challenge.keyHandle)) {
3361320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        this.cachedError_[challenge.keyHandle] = code;
3375c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      }
3385c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
3395c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
3405c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
341116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  var self = this;
3425c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  switch (code) {
3435c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    case DeviceStatusCodes.GONE_STATUS:
3445c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.goToError_(code);
3455c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      break;
3465c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3475c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    case DeviceStatusCodes.TIMEOUT_STATUS:
348010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)      // TODO: On a TIMEOUT_STATUS, sync first, then retry.
3495c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    case DeviceStatusCodes.BUSY_STATUS:
3505c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      this.doSign_(this.challengeIndex_);
3515c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      break;
3525c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3535c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    case DeviceStatusCodes.OK_STATUS:
3545c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      if (this.forEnroll_) {
3555c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.goToError_(code);
3565c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      } else {
3575c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.goToSuccess_(code, this.challenges_[challengeIndex], opt_info);
3585c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      }
3595c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      break;
3605c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3615c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    case DeviceStatusCodes.WAIT_TOUCH_STATUS:
362116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      window.setTimeout(function() {
363116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch        self.doSign_(self.challengeIndex_);
364116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      }, SingleGnubbySigner.SIGN_DELAY_MILLIS);
3655c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      break;
3665c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3675c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    case DeviceStatusCodes.WRONG_DATA_STATUS:
3681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    case DeviceStatusCodes.WRONG_LENGTH_STATUS:
3695c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      if (this.challengeIndex_ < this.challenges_.length - 1) {
3705c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.doSign_(++this.challengeIndex_);
3715c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      } else if (this.forEnroll_) {
372116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch        this.goToSuccess_(code);
3735c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      } else {
3745c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.goToError_(code);
3755c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      }
3765c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      break;
3775c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3785c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    default:
3795c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      if (this.forEnroll_) {
3801320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        this.goToError_(code, true);
3815c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      } else if (this.challengeIndex_ < this.challenges_.length - 1) {
3825c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu        this.doSign_(++this.challengeIndex_);
3835c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      } else {
3841320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        this.goToError_(code, true);
3855c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu      }
3865c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
3875c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
3885c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
3895c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
3905c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Switches to the error state, and notifies caller.
391010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {number} code Error code
3921320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci * @param {boolean=} opt_warn Whether to warn in the console about the error.
3935c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
3945c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
3951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano TucciSingleGnubbySigner.prototype.goToError_ = function(code, opt_warn) {
396116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  this.state_ = SingleGnubbySigner.State.COMPLETE;
3971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  var logFn = opt_warn ? console.warn.bind(console) : console.log.bind(console);
3981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  logFn(UTIL_fmt('failed (' + code.toString(16) + ')'));
3995c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // Since this gnubby can no longer produce a useful result, go ahead and
4005c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  // close it.
4015c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.close();
402116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  var result = { code: code };
403116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  this.completeCb_(result);
4045c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
4055c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu
4065c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu/**
4075c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * Switches to the success state, and notifies caller.
408010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {number} code Status code
409010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {SignHelperChallenge=} opt_challenge The challenge signed
410010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * @param {ArrayBuffer=} opt_info Optional result data
4115c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu * @private
4125c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu */
4135c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo LiuSingleGnubbySigner.prototype.goToSuccess_ =
4145c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    function(code, opt_challenge, opt_info) {
415116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  this.state_ = SingleGnubbySigner.State.COMPLETE;
4165c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  console.log(UTIL_fmt('success (' + code.toString(16) + ')'));
417116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  var result = { code: code, gnubby: this.gnubby_ };
4185c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  if (opt_challenge || opt_info) {
4195c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (opt_challenge) {
420116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      result['challenge'] = opt_challenge;
4215c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
4225c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    if (opt_info) {
423116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      result['info'] = opt_info;
4245c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu    }
4255c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  }
426116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  this.completeCb_(result);
427116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  // this.gnubby_ is now owned by completeCb_.
4285c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu  this.gnubby_ = null;
4295c02ac1a9c1b504631c0a3d2b6e737b5d738bae1Bo Liu};
430