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