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}