summaryrefslogtreecommitdiffstats
path: root/wifi/java/android/net/wifi/SupplicantStateTracker.java
diff options
context:
space:
mode:
Diffstat (limited to 'wifi/java/android/net/wifi/SupplicantStateTracker.java')
-rw-r--r--wifi/java/android/net/wifi/SupplicantStateTracker.java333
1 files changed, 333 insertions, 0 deletions
diff --git a/wifi/java/android/net/wifi/SupplicantStateTracker.java b/wifi/java/android/net/wifi/SupplicantStateTracker.java
new file mode 100644
index 0000000..a83a0ad
--- /dev/null
+++ b/wifi/java/android/net/wifi/SupplicantStateTracker.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi;
+
+import com.android.internal.util.HierarchicalState;
+import com.android.internal.util.HierarchicalStateMachine;
+
+import android.net.wifi.WifiStateMachine.StateChangeResult;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * Tracks the state changes in supplicant and provides functionality
+ * that is based on these state changes:
+ * - detect a failed WPA handshake that loops indefinitely
+ * - password failure handling
+ * - Enable networks after a WPS success/failure
+ */
+class SupplicantStateTracker extends HierarchicalStateMachine {
+
+ private static final String TAG = "SupplicantStateTracker";
+ private static final boolean DBG = false;
+
+ private WifiStateMachine mWifiStateMachine;
+ private int mPasswordFailuresCount = 0;
+ /* Indicates authentication failure in supplicant broadcast.
+ * TODO: enhance auth failure reporting to include notification
+ * for all type of failures: EAP, WPS & WPA networks */
+ private boolean mAuthFailureInSupplicantBroadcast = false;
+
+ /* Maximum retries on a password failure notification */
+ private static final int MAX_RETRIES_ON_PASSWORD_FAILURE = 2;
+
+ /* Track if WPS was started since we need to re-enable networks
+ * and load configuration afterwards */
+ private boolean mWpsStarted = false;
+
+ private Context mContext;
+
+ private HierarchicalState mUninitializedState = new UninitializedState();
+ private HierarchicalState mDefaultState = new DefaultState();
+ private HierarchicalState mInactiveState = new InactiveState();
+ private HierarchicalState mDisconnectState = new DisconnectedState();
+ private HierarchicalState mScanState = new ScanState();
+ private HierarchicalState mHandshakeState = new HandshakeState();
+ private HierarchicalState mCompletedState = new CompletedState();
+ private HierarchicalState mDormantState = new DormantState();
+
+ public SupplicantStateTracker(Context context, WifiStateMachine wsm, Handler target) {
+ super(TAG, target.getLooper());
+
+ mContext = context;
+ mWifiStateMachine = wsm;
+ addState(mDefaultState);
+ addState(mUninitializedState, mDefaultState);
+ addState(mInactiveState, mDefaultState);
+ addState(mDisconnectState, mDefaultState);
+ addState(mScanState, mDefaultState);
+ addState(mHandshakeState, mDefaultState);
+ addState(mCompletedState, mDefaultState);
+ addState(mDormantState, mDefaultState);
+
+ setInitialState(mUninitializedState);
+
+ //start the state machine
+ start();
+ }
+
+ public void resetSupplicantState() {
+ transitionTo(mUninitializedState);
+ }
+
+
+ private void transitionOnSupplicantStateChange(StateChangeResult stateChangeResult) {
+ SupplicantState supState = (SupplicantState) stateChangeResult.state;
+
+ if (DBG) Log.d(TAG, "Supplicant state: " + supState.toString() + "\n");
+
+ switch (supState) {
+ case DISCONNECTED:
+ transitionTo(mDisconnectState);
+ break;
+ case SCANNING:
+ transitionTo(mScanState);
+ break;
+ case ASSOCIATING:
+ case ASSOCIATED:
+ case FOUR_WAY_HANDSHAKE:
+ case GROUP_HANDSHAKE:
+ transitionTo(mHandshakeState);
+ break;
+ case COMPLETED:
+ transitionTo(mCompletedState);
+ break;
+ case DORMANT:
+ transitionTo(mDormantState);
+ break;
+ case INACTIVE:
+ transitionTo(mInactiveState);
+ break;
+ case UNINITIALIZED:
+ case INVALID:
+ transitionTo(mUninitializedState);
+ break;
+ default:
+ Log.e(TAG, "Unknown supplicant state " + supState);
+ break;
+ }
+ }
+
+ private void sendSupplicantStateChangedBroadcast(StateChangeResult sc, boolean failedAuth) {
+ Intent intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable)sc.state);
+ if (failedAuth) {
+ intent.putExtra(
+ WifiManager.EXTRA_SUPPLICANT_ERROR,
+ WifiManager.ERROR_AUTHENTICATING);
+ }
+ mContext.sendStickyBroadcast(intent);
+ }
+
+ /********************************************************
+ * HSM states
+ *******************************************************/
+
+ class DefaultState extends HierarchicalState {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+ switch (message.what) {
+ case WifiStateMachine.PASSWORD_MAY_BE_INCORRECT_EVENT:
+ mPasswordFailuresCount++;
+ mAuthFailureInSupplicantBroadcast = true;
+ break;
+ case WifiStateMachine.CMD_START_WPS_PBC:
+ case WifiStateMachine.CMD_START_WPS_PIN_FROM_AP:
+ case WifiStateMachine.CMD_START_WPS_PIN_FROM_DEVICE:
+ mWpsStarted = true;
+ break;
+ case WifiStateMachine.SUPPLICANT_STATE_CHANGE_EVENT:
+ StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+ sendSupplicantStateChangedBroadcast(stateChangeResult,
+ mAuthFailureInSupplicantBroadcast);
+ mAuthFailureInSupplicantBroadcast = false;
+ transitionOnSupplicantStateChange(stateChangeResult);
+ break;
+ default:
+ Log.e(TAG, "Ignoring " + message);
+ break;
+ }
+ return HANDLED;
+ }
+ }
+
+ class UninitializedState extends HierarchicalState {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ mWifiStateMachine.setNetworkAvailable(false);
+ }
+ @Override
+ public void exit() {
+ mWifiStateMachine.setNetworkAvailable(true);
+ }
+ }
+
+ class InactiveState extends HierarchicalState {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ /* A failed WPS connection */
+ if (mWpsStarted) {
+ Log.e(TAG, "WPS set up failed, enabling other networks");
+ WifiConfigStore.enableAllNetworks();
+ mWpsStarted = false;
+ }
+ mWifiStateMachine.setNetworkAvailable(false);
+ }
+ @Override
+ public void exit() {
+ mWifiStateMachine.setNetworkAvailable(true);
+ }
+ }
+
+
+ class DisconnectedState extends HierarchicalState {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ /* If a disconnect event happens after password key failure
+ * exceeds maximum retries, disable the network
+ */
+
+ Message message = getCurrentMessage();
+ StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+
+ if (mPasswordFailuresCount >= MAX_RETRIES_ON_PASSWORD_FAILURE) {
+ Log.d(TAG, "Failed to authenticate, disabling network " +
+ stateChangeResult.networkId);
+ WifiConfigStore.disableNetwork(stateChangeResult.networkId);
+ mPasswordFailuresCount = 0;
+ }
+ }
+ }
+
+ class ScanState extends HierarchicalState {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ }
+ }
+
+ class HandshakeState extends HierarchicalState {
+ /**
+ * The max number of the WPA supplicant loop iterations before we
+ * decide that the loop should be terminated:
+ */
+ private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4;
+ private int mLoopDetectIndex;
+ private int mLoopDetectCount;
+
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ mLoopDetectIndex = 0;
+ mLoopDetectCount = 0;
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+ switch (message.what) {
+ case WifiStateMachine.SUPPLICANT_STATE_CHANGE_EVENT:
+ StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+ SupplicantState state = (SupplicantState) stateChangeResult.state;
+ if (state == SupplicantState.ASSOCIATING ||
+ state == SupplicantState.ASSOCIATED ||
+ state == SupplicantState.FOUR_WAY_HANDSHAKE ||
+ state == SupplicantState.GROUP_HANDSHAKE) {
+ if (mLoopDetectIndex > state.ordinal()) {
+ mLoopDetectCount++;
+ }
+ if (mLoopDetectCount > MAX_SUPPLICANT_LOOP_ITERATIONS) {
+ Log.d(TAG, "Supplicant loop detected, disabling network " +
+ stateChangeResult.networkId);
+ WifiConfigStore.disableNetwork(stateChangeResult.networkId);
+ }
+ mLoopDetectIndex = state.ordinal();
+ sendSupplicantStateChangedBroadcast(stateChangeResult,
+ mAuthFailureInSupplicantBroadcast);
+ } else {
+ //Have the DefaultState handle the transition
+ return NOT_HANDLED;
+ }
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ class CompletedState extends HierarchicalState {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ /* Reset password failure count */
+ mPasswordFailuresCount = 0;
+
+ /* A successful WPS connection */
+ if (mWpsStarted) {
+ WifiConfigStore.enableAllNetworks();
+ WifiConfigStore.loadConfiguredNetworks();
+ mWpsStarted = false;
+ }
+ }
+ @Override
+ public boolean processMessage(Message message) {
+ if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
+ switch(message.what) {
+ case WifiStateMachine.SUPPLICANT_STATE_CHANGE_EVENT:
+ StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
+ SupplicantState state = (SupplicantState) stateChangeResult.state;
+ sendSupplicantStateChangedBroadcast(stateChangeResult,
+ mAuthFailureInSupplicantBroadcast);
+ /* Ignore a re-auth in completed state */
+ if (state == SupplicantState.ASSOCIATING ||
+ state == SupplicantState.ASSOCIATED ||
+ state == SupplicantState.FOUR_WAY_HANDSHAKE ||
+ state == SupplicantState.GROUP_HANDSHAKE ||
+ state == SupplicantState.COMPLETED) {
+ break;
+ }
+ transitionOnSupplicantStateChange(stateChangeResult);
+ break;
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+ }
+
+ //TODO: remove after getting rid of the state in supplicant
+ class DormantState extends HierarchicalState {
+ @Override
+ public void enter() {
+ if (DBG) Log.d(TAG, getName() + "\n");
+ }
+ }
+} \ No newline at end of file