SupplicantStateTracker.java revision 02fb46a297c4f645f2a30b574151401dd0978521
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.net.wifi; 18 19import com.android.internal.util.HierarchicalState; 20import com.android.internal.util.HierarchicalStateMachine; 21 22import android.net.wifi.WifiStateMachine.StateChangeResult; 23import android.content.Context; 24import android.content.Intent; 25import android.os.Handler; 26import android.os.Message; 27import android.os.Parcelable; 28import android.util.Log; 29 30/** 31 * Tracks the state changes in supplicant and provides functionality 32 * that is based on these state changes: 33 * - detect a failed WPA handshake that loops indefinitely 34 * - password failure handling 35 */ 36class SupplicantStateTracker extends HierarchicalStateMachine { 37 38 private static final String TAG = "SupplicantStateTracker"; 39 private static final boolean DBG = false; 40 41 private WifiStateMachine mWifiStateMachine; 42 private int mPasswordFailuresCount = 0; 43 /* Indicates authentication failure in supplicant broadcast. 44 * TODO: enhance auth failure reporting to include notification 45 * for all type of failures: EAP, WPS & WPA networks */ 46 private boolean mAuthFailureInSupplicantBroadcast = false; 47 48 /* Maximum retries on a password failure notification */ 49 private static final int MAX_RETRIES_ON_PASSWORD_FAILURE = 2; 50 51 private Context mContext; 52 53 private HierarchicalState mUninitializedState = new UninitializedState(); 54 private HierarchicalState mDefaultState = new DefaultState(); 55 private HierarchicalState mInactiveState = new InactiveState(); 56 private HierarchicalState mDisconnectState = new DisconnectedState(); 57 private HierarchicalState mScanState = new ScanState(); 58 private HierarchicalState mHandshakeState = new HandshakeState(); 59 private HierarchicalState mCompletedState = new CompletedState(); 60 private HierarchicalState mDormantState = new DormantState(); 61 62 public SupplicantStateTracker(Context context, WifiStateMachine wsm, Handler target) { 63 super(TAG, target.getLooper()); 64 65 mContext = context; 66 mWifiStateMachine = wsm; 67 addState(mDefaultState); 68 addState(mUninitializedState, mDefaultState); 69 addState(mInactiveState, mDefaultState); 70 addState(mDisconnectState, mDefaultState); 71 addState(mScanState, mDefaultState); 72 addState(mHandshakeState, mDefaultState); 73 addState(mCompletedState, mDefaultState); 74 addState(mDormantState, mDefaultState); 75 76 setInitialState(mUninitializedState); 77 78 //start the state machine 79 start(); 80 } 81 82 public void resetSupplicantState() { 83 transitionTo(mUninitializedState); 84 } 85 86 87 private void transitionOnSupplicantStateChange(StateChangeResult stateChangeResult) { 88 SupplicantState supState = (SupplicantState) stateChangeResult.state; 89 90 if (DBG) Log.d(TAG, "Supplicant state: " + supState.toString() + "\n"); 91 92 switch (supState) { 93 case DISCONNECTED: 94 transitionTo(mDisconnectState); 95 break; 96 case SCANNING: 97 transitionTo(mScanState); 98 break; 99 case ASSOCIATING: 100 case ASSOCIATED: 101 case FOUR_WAY_HANDSHAKE: 102 case GROUP_HANDSHAKE: 103 transitionTo(mHandshakeState); 104 break; 105 case COMPLETED: 106 transitionTo(mCompletedState); 107 break; 108 case DORMANT: 109 transitionTo(mDormantState); 110 break; 111 case INACTIVE: 112 transitionTo(mInactiveState); 113 break; 114 case UNINITIALIZED: 115 case INVALID: 116 transitionTo(mUninitializedState); 117 break; 118 default: 119 Log.e(TAG, "Unknown supplicant state " + supState); 120 break; 121 } 122 } 123 124 private void sendSupplicantStateChangedBroadcast(StateChangeResult sc, boolean failedAuth) { 125 Intent intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); 126 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT 127 | Intent.FLAG_RECEIVER_REPLACE_PENDING); 128 intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable)sc.state); 129 if (failedAuth) { 130 intent.putExtra( 131 WifiManager.EXTRA_SUPPLICANT_ERROR, 132 WifiManager.ERROR_AUTHENTICATING); 133 } 134 mContext.sendStickyBroadcast(intent); 135 } 136 137 /******************************************************** 138 * HSM states 139 *******************************************************/ 140 141 class DefaultState extends HierarchicalState { 142 @Override 143 public void enter() { 144 if (DBG) Log.d(TAG, getName() + "\n"); 145 } 146 @Override 147 public boolean processMessage(Message message) { 148 if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); 149 switch (message.what) { 150 case WifiStateMachine.PASSWORD_MAY_BE_INCORRECT_EVENT: 151 mPasswordFailuresCount++; 152 mAuthFailureInSupplicantBroadcast = true; 153 break; 154 case WifiStateMachine.SUPPLICANT_STATE_CHANGE_EVENT: 155 StateChangeResult stateChangeResult = (StateChangeResult) message.obj; 156 sendSupplicantStateChangedBroadcast(stateChangeResult, 157 mAuthFailureInSupplicantBroadcast); 158 mAuthFailureInSupplicantBroadcast = false; 159 transitionOnSupplicantStateChange(stateChangeResult); 160 break; 161 default: 162 Log.e(TAG, "Ignoring " + message); 163 break; 164 } 165 return HANDLED; 166 } 167 } 168 169 class UninitializedState extends HierarchicalState { 170 @Override 171 public void enter() { 172 if (DBG) Log.d(TAG, getName() + "\n"); 173 mWifiStateMachine.setNetworkAvailable(false); 174 } 175 @Override 176 public void exit() { 177 mWifiStateMachine.setNetworkAvailable(true); 178 } 179 } 180 181 class InactiveState extends HierarchicalState { 182 @Override 183 public void enter() { 184 if (DBG) Log.d(TAG, getName() + "\n"); 185 mWifiStateMachine.setNetworkAvailable(false); 186 } 187 @Override 188 public void exit() { 189 mWifiStateMachine.setNetworkAvailable(true); 190 } 191 } 192 193 194 class DisconnectedState extends HierarchicalState { 195 @Override 196 public void enter() { 197 if (DBG) Log.d(TAG, getName() + "\n"); 198 /* If a disconnect event happens after password key failure 199 * exceeds maximum retries, disable the network 200 */ 201 202 Message message = getCurrentMessage(); 203 StateChangeResult stateChangeResult = (StateChangeResult) message.obj; 204 205 if (mPasswordFailuresCount >= MAX_RETRIES_ON_PASSWORD_FAILURE) { 206 Log.d(TAG, "Failed to authenticate, disabling network " + 207 stateChangeResult.networkId); 208 WifiConfigStore.disableNetwork(stateChangeResult.networkId); 209 mPasswordFailuresCount = 0; 210 } 211 } 212 } 213 214 class ScanState extends HierarchicalState { 215 @Override 216 public void enter() { 217 if (DBG) Log.d(TAG, getName() + "\n"); 218 } 219 } 220 221 class HandshakeState extends HierarchicalState { 222 /** 223 * The max number of the WPA supplicant loop iterations before we 224 * decide that the loop should be terminated: 225 */ 226 private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4; 227 private int mLoopDetectIndex; 228 private int mLoopDetectCount; 229 230 @Override 231 public void enter() { 232 if (DBG) Log.d(TAG, getName() + "\n"); 233 mLoopDetectIndex = 0; 234 mLoopDetectCount = 0; 235 } 236 @Override 237 public boolean processMessage(Message message) { 238 if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); 239 switch (message.what) { 240 case WifiStateMachine.SUPPLICANT_STATE_CHANGE_EVENT: 241 StateChangeResult stateChangeResult = (StateChangeResult) message.obj; 242 SupplicantState state = (SupplicantState) stateChangeResult.state; 243 if (state == SupplicantState.ASSOCIATING || 244 state == SupplicantState.ASSOCIATED || 245 state == SupplicantState.FOUR_WAY_HANDSHAKE || 246 state == SupplicantState.GROUP_HANDSHAKE) { 247 if (mLoopDetectIndex > state.ordinal()) { 248 mLoopDetectCount++; 249 } 250 if (mLoopDetectCount > MAX_SUPPLICANT_LOOP_ITERATIONS) { 251 Log.d(TAG, "Supplicant loop detected, disabling network " + 252 stateChangeResult.networkId); 253 WifiConfigStore.disableNetwork(stateChangeResult.networkId); 254 } 255 mLoopDetectIndex = state.ordinal(); 256 sendSupplicantStateChangedBroadcast(stateChangeResult, 257 mAuthFailureInSupplicantBroadcast); 258 } else { 259 //Have the DefaultState handle the transition 260 return NOT_HANDLED; 261 } 262 break; 263 default: 264 return NOT_HANDLED; 265 } 266 return HANDLED; 267 } 268 } 269 270 class CompletedState extends HierarchicalState { 271 @Override 272 public void enter() { 273 if (DBG) Log.d(TAG, getName() + "\n"); 274 /* Reset password failure count */ 275 mPasswordFailuresCount = 0; 276 } 277 @Override 278 public boolean processMessage(Message message) { 279 if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); 280 switch(message.what) { 281 case WifiStateMachine.SUPPLICANT_STATE_CHANGE_EVENT: 282 StateChangeResult stateChangeResult = (StateChangeResult) message.obj; 283 SupplicantState state = (SupplicantState) stateChangeResult.state; 284 sendSupplicantStateChangedBroadcast(stateChangeResult, 285 mAuthFailureInSupplicantBroadcast); 286 /* Ignore a re-auth in completed state */ 287 if (state == SupplicantState.ASSOCIATING || 288 state == SupplicantState.ASSOCIATED || 289 state == SupplicantState.FOUR_WAY_HANDSHAKE || 290 state == SupplicantState.GROUP_HANDSHAKE || 291 state == SupplicantState.COMPLETED) { 292 break; 293 } 294 transitionOnSupplicantStateChange(stateChangeResult); 295 break; 296 default: 297 return NOT_HANDLED; 298 } 299 return HANDLED; 300 } 301 } 302 303 //TODO: remove after getting rid of the state in supplicant 304 class DormantState extends HierarchicalState { 305 @Override 306 public void enter() { 307 if (DBG) Log.d(TAG, getName() + "\n"); 308 } 309 } 310}