michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.fxa.login; michael@0: michael@0: import java.security.NoSuchAlgorithmException; michael@0: import java.util.HashSet; michael@0: import java.util.Set; michael@0: michael@0: import org.mozilla.gecko.background.fxa.FxAccountClient; michael@0: import org.mozilla.gecko.browserid.BrowserIDKeyPair; michael@0: import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.Transition; michael@0: import org.mozilla.gecko.fxa.login.State.StateLabel; michael@0: michael@0: public class FxAccountLoginStateMachine { michael@0: public static final String LOG_TAG = FxAccountLoginStateMachine.class.getSimpleName(); michael@0: michael@0: public interface LoginStateMachineDelegate { michael@0: public FxAccountClient getClient(); michael@0: public long getCertificateDurationInMilliseconds(); michael@0: public long getAssertionDurationInMilliseconds(); michael@0: public void handleTransition(Transition transition, State state); michael@0: public void handleFinal(State state); michael@0: public BrowserIDKeyPair generateKeyPair() throws NoSuchAlgorithmException; michael@0: } michael@0: michael@0: public static class ExecuteDelegate { michael@0: protected final LoginStateMachineDelegate delegate; michael@0: protected final StateLabel desiredStateLabel; michael@0: // It's as difficult to detect arbitrary cycles as repeated states. michael@0: protected final Set stateLabelsSeen = new HashSet(); michael@0: michael@0: protected ExecuteDelegate(StateLabel initialStateLabel, StateLabel desiredStateLabel, LoginStateMachineDelegate delegate) { michael@0: this.delegate = delegate; michael@0: this.desiredStateLabel = desiredStateLabel; michael@0: this.stateLabelsSeen.add(initialStateLabel); michael@0: } michael@0: michael@0: public FxAccountClient getClient() { michael@0: return delegate.getClient(); michael@0: } michael@0: michael@0: public long getCertificateDurationInMilliseconds() { michael@0: return delegate.getCertificateDurationInMilliseconds(); michael@0: } michael@0: michael@0: public long getAssertionDurationInMilliseconds() { michael@0: return delegate.getAssertionDurationInMilliseconds(); michael@0: } michael@0: michael@0: public BrowserIDKeyPair generateKeyPair() throws NoSuchAlgorithmException { michael@0: return delegate.generateKeyPair(); michael@0: } michael@0: michael@0: public void handleTransition(Transition transition, State state) { michael@0: // Always trigger the transition callback. michael@0: delegate.handleTransition(transition, state); michael@0: michael@0: // Possibly trigger the final callback. We trigger if we're at our desired michael@0: // state, or if we've seen this state before. michael@0: StateLabel stateLabel = state.getStateLabel(); michael@0: if (stateLabel == desiredStateLabel || stateLabelsSeen.contains(stateLabel)) { michael@0: delegate.handleFinal(state); michael@0: return; michael@0: } michael@0: michael@0: // If this wasn't the last state, leave a bread crumb and move on to the michael@0: // next state. michael@0: stateLabelsSeen.add(stateLabel); michael@0: state.execute(this); michael@0: } michael@0: } michael@0: michael@0: public void advance(State initialState, final StateLabel desiredStateLabel, final LoginStateMachineDelegate delegate) { michael@0: if (initialState.getStateLabel() == desiredStateLabel) { michael@0: // We're already where we want to be! michael@0: delegate.handleFinal(initialState); michael@0: return; michael@0: } michael@0: ExecuteDelegate executeDelegate = new ExecuteDelegate(initialState.getStateLabel(), desiredStateLabel, delegate); michael@0: initialState.execute(executeDelegate); michael@0: } michael@0: }