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