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.State;
20import com.android.internal.util.StateMachine;
21
22import android.net.wifi.StateChangeResult;
23import android.content.Context;
24import android.content.Intent;
25import android.os.Handler;
26import android.os.Message;
27import android.os.Parcelable;
28import android.os.UserHandle;
29import android.util.Log;
30
31/**
32 * Tracks the state changes in supplicant and provides functionality
33 * that is based on these state changes:
34 * - detect a failed WPA handshake that loops indefinitely
35 * - authentication failure handling
36 */
37class SupplicantStateTracker extends StateMachine {
38
39    private static final String TAG = "SupplicantStateTracker";
40    private static final boolean DBG = false;
41
42    private WifiStateMachine mWifiStateMachine;
43    private WifiConfigStore mWifiConfigStore;
44    private int mAuthenticationFailuresCount = 0;
45    /* Indicates authentication failure in supplicant broadcast.
46     * TODO: enhance auth failure reporting to include notification
47     * for all type of failures: EAP, WPS & WPA networks */
48    private boolean mAuthFailureInSupplicantBroadcast = false;
49
50    /* Maximum retries on a authentication failure notification */
51    private static final int MAX_RETRIES_ON_AUTHENTICATION_FAILURE = 2;
52
53    /* Tracks if networks have been disabled during a connection */
54    private boolean mNetworksDisabledDuringConnect = false;
55
56    private Context mContext;
57
58    private State mUninitializedState = new UninitializedState();
59    private State mDefaultState = new DefaultState();
60    private State mInactiveState = new InactiveState();
61    private State mDisconnectState = new DisconnectedState();
62    private State mScanState = new ScanState();
63    private State mHandshakeState = new HandshakeState();
64    private State mCompletedState = new CompletedState();
65    private State mDormantState = new DormantState();
66
67    public SupplicantStateTracker(Context c, WifiStateMachine wsm, WifiConfigStore wcs, Handler t) {
68        super(TAG, t.getLooper());
69
70        mContext = c;
71        mWifiStateMachine = wsm;
72        mWifiConfigStore = wcs;
73        addState(mDefaultState);
74            addState(mUninitializedState, mDefaultState);
75            addState(mInactiveState, mDefaultState);
76            addState(mDisconnectState, mDefaultState);
77            addState(mScanState, mDefaultState);
78            addState(mHandshakeState, mDefaultState);
79            addState(mCompletedState, mDefaultState);
80            addState(mDormantState, mDefaultState);
81
82        setInitialState(mUninitializedState);
83
84        //start the state machine
85        start();
86    }
87
88    private void handleNetworkConnectionFailure(int netId) {
89        /* If other networks disabled during connection, enable them */
90        if (mNetworksDisabledDuringConnect) {
91            mWifiConfigStore.enableAllNetworks();
92            mNetworksDisabledDuringConnect = false;
93        }
94        /* Disable failed network */
95        mWifiConfigStore.disableNetwork(netId, WifiConfiguration.DISABLED_AUTH_FAILURE);
96    }
97
98    private void transitionOnSupplicantStateChange(StateChangeResult stateChangeResult) {
99        SupplicantState supState = (SupplicantState) stateChangeResult.state;
100
101        if (DBG) Log.d(TAG, "Supplicant state: " + supState.toString() + "\n");
102
103        switch (supState) {
104           case DISCONNECTED:
105                transitionTo(mDisconnectState);
106                break;
107            case INTERFACE_DISABLED:
108                //we should have received a disconnection already, do nothing
109                break;
110            case SCANNING:
111                transitionTo(mScanState);
112                break;
113            case AUTHENTICATING:
114            case ASSOCIATING:
115            case ASSOCIATED:
116            case FOUR_WAY_HANDSHAKE:
117            case GROUP_HANDSHAKE:
118                transitionTo(mHandshakeState);
119                break;
120            case COMPLETED:
121                transitionTo(mCompletedState);
122                break;
123            case DORMANT:
124                transitionTo(mDormantState);
125                break;
126            case INACTIVE:
127                transitionTo(mInactiveState);
128                break;
129            case UNINITIALIZED:
130            case INVALID:
131                transitionTo(mUninitializedState);
132                break;
133            default:
134                Log.e(TAG, "Unknown supplicant state " + supState);
135                break;
136        }
137    }
138
139    private void sendSupplicantStateChangedBroadcast(SupplicantState state, boolean failedAuth) {
140        Intent intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
141        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
142                | Intent.FLAG_RECEIVER_REPLACE_PENDING);
143        intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable) state);
144        if (failedAuth) {
145            intent.putExtra(
146                WifiManager.EXTRA_SUPPLICANT_ERROR,
147                WifiManager.ERROR_AUTHENTICATING);
148        }
149        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
150    }
151
152    /********************************************************
153     * HSM states
154     *******************************************************/
155
156    class DefaultState extends State {
157        @Override
158         public void enter() {
159             if (DBG) Log.d(TAG, getName() + "\n");
160         }
161        @Override
162        public boolean processMessage(Message message) {
163            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
164            switch (message.what) {
165                case WifiMonitor.AUTHENTICATION_FAILURE_EVENT:
166                    mAuthenticationFailuresCount++;
167                    mAuthFailureInSupplicantBroadcast = true;
168                    break;
169                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
170                    StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
171                    SupplicantState state = stateChangeResult.state;
172                    sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast);
173                    mAuthFailureInSupplicantBroadcast = false;
174                    transitionOnSupplicantStateChange(stateChangeResult);
175                    break;
176                case WifiStateMachine.CMD_RESET_SUPPLICANT_STATE:
177                    transitionTo(mUninitializedState);
178                    break;
179                case WifiManager.CONNECT_NETWORK:
180                    mNetworksDisabledDuringConnect = true;
181                    break;
182                default:
183                    Log.e(TAG, "Ignoring " + message);
184                    break;
185            }
186            return HANDLED;
187        }
188    }
189
190    /*
191     * This indicates that the supplicant state as seen
192     * by the framework is not initialized yet. We are
193     * in this state right after establishing a control
194     * channel connection before any supplicant events
195     * or after we have lost the control channel
196     * connection to the supplicant
197     */
198    class UninitializedState extends State {
199        @Override
200         public void enter() {
201             if (DBG) Log.d(TAG, getName() + "\n");
202         }
203    }
204
205    class InactiveState extends State {
206        @Override
207         public void enter() {
208             if (DBG) Log.d(TAG, getName() + "\n");
209         }
210    }
211
212    class DisconnectedState extends State {
213        @Override
214         public void enter() {
215             if (DBG) Log.d(TAG, getName() + "\n");
216             /* If a disconnect event happens after authentication failure
217              * exceeds maximum retries, disable the network
218              */
219             Message message = getCurrentMessage();
220             StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
221
222             if (mAuthenticationFailuresCount >= MAX_RETRIES_ON_AUTHENTICATION_FAILURE) {
223                 Log.d(TAG, "Failed to authenticate, disabling network " +
224                         stateChangeResult.networkId);
225                 handleNetworkConnectionFailure(stateChangeResult.networkId);
226                 mAuthenticationFailuresCount = 0;
227             }
228         }
229    }
230
231    class ScanState extends State {
232        @Override
233         public void enter() {
234             if (DBG) Log.d(TAG, getName() + "\n");
235         }
236    }
237
238    class HandshakeState extends State {
239        /**
240         * The max number of the WPA supplicant loop iterations before we
241         * decide that the loop should be terminated:
242         */
243        private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4;
244        private int mLoopDetectIndex;
245        private int mLoopDetectCount;
246
247        @Override
248         public void enter() {
249             if (DBG) Log.d(TAG, getName() + "\n");
250             mLoopDetectIndex = 0;
251             mLoopDetectCount = 0;
252         }
253        @Override
254        public boolean processMessage(Message message) {
255            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
256            switch (message.what) {
257                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
258                    StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
259                    SupplicantState state = stateChangeResult.state;
260                    if (SupplicantState.isHandshakeState(state)) {
261                        if (mLoopDetectIndex > state.ordinal()) {
262                            mLoopDetectCount++;
263                        }
264                        if (mLoopDetectCount > MAX_SUPPLICANT_LOOP_ITERATIONS) {
265                            Log.d(TAG, "Supplicant loop detected, disabling network " +
266                                    stateChangeResult.networkId);
267                            handleNetworkConnectionFailure(stateChangeResult.networkId);
268                        }
269                        mLoopDetectIndex = state.ordinal();
270                        sendSupplicantStateChangedBroadcast(state,
271                                mAuthFailureInSupplicantBroadcast);
272                    } else {
273                        //Have the DefaultState handle the transition
274                        return NOT_HANDLED;
275                    }
276                    break;
277                default:
278                    return NOT_HANDLED;
279            }
280            return HANDLED;
281        }
282    }
283
284    class CompletedState extends State {
285        @Override
286         public void enter() {
287             if (DBG) Log.d(TAG, getName() + "\n");
288             /* Reset authentication failure count */
289             mAuthenticationFailuresCount = 0;
290             if (mNetworksDisabledDuringConnect) {
291                 mWifiConfigStore.enableAllNetworks();
292                 mNetworksDisabledDuringConnect = false;
293             }
294        }
295        @Override
296        public boolean processMessage(Message message) {
297            if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
298            switch(message.what) {
299                case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
300                    StateChangeResult stateChangeResult = (StateChangeResult) message.obj;
301                    SupplicantState state = stateChangeResult.state;
302                    sendSupplicantStateChangedBroadcast(state, mAuthFailureInSupplicantBroadcast);
303                    /* Ignore any connecting state in completed state. Group re-keying
304                     * events and other auth events that do not affect connectivity are
305                     * ignored
306                     */
307                    if (SupplicantState.isConnecting(state)) {
308                        break;
309                    }
310                    transitionOnSupplicantStateChange(stateChangeResult);
311                    break;
312                case WifiStateMachine.CMD_RESET_SUPPLICANT_STATE:
313                    sendSupplicantStateChangedBroadcast(SupplicantState.DISCONNECTED, false);
314                    transitionTo(mUninitializedState);
315                    break;
316                default:
317                    return NOT_HANDLED;
318            }
319            return HANDLED;
320        }
321    }
322
323    //TODO: remove after getting rid of the state in supplicant
324    class DormantState extends State {
325        @Override
326        public void enter() {
327            if (DBG) Log.d(TAG, getName() + "\n");
328        }
329    }
330}
331