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