WifiMonitor.java revision 0d25534fed91f636def5776ddc4605005bd7471c
1/*
2 * Copyright (C) 2008 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 android.util.Log;
20import android.util.Config;
21import android.net.NetworkInfo;
22
23import java.util.regex.Pattern;
24import java.util.regex.Matcher;
25
26/**
27 * Listens for events from the wpa_supplicant server, and passes them on
28 * to the {@link WifiStateMachine} for handling. Runs in its own thread.
29 *
30 * @hide
31 */
32public class WifiMonitor {
33
34    private static final String TAG = "WifiMonitor";
35
36    /** Events we receive from the supplicant daemon */
37
38    private static final int CONNECTED    = 1;
39    private static final int DISCONNECTED = 2;
40    private static final int STATE_CHANGE = 3;
41    private static final int SCAN_RESULTS = 4;
42    private static final int LINK_SPEED   = 5;
43    private static final int TERMINATING  = 6;
44    private static final int DRIVER_STATE = 7;
45    private static final int UNKNOWN      = 8;
46
47    /** All events coming from the supplicant start with this prefix */
48    private static final String eventPrefix = "CTRL-EVENT-";
49    private static final int eventPrefixLen = eventPrefix.length();
50
51    /** All WPA events coming from the supplicant start with this prefix */
52    private static final String wpaEventPrefix = "WPA:";
53    private static final String passwordKeyMayBeIncorrectEvent =
54       "pre-shared key may be incorrect";
55
56    /**
57     * Names of events from wpa_supplicant (minus the prefix). In the
58     * format descriptions, * &quot;<code>x</code>&quot;
59     * designates a dynamic value that needs to be parsed out from the event
60     * string
61     */
62    /**
63     * <pre>
64     * CTRL-EVENT-CONNECTED - Connection to xx:xx:xx:xx:xx:xx completed
65     * </pre>
66     * <code>xx:xx:xx:xx:xx:xx</code> is the BSSID of the associated access point
67     */
68    private static final String connectedEvent =    "CONNECTED";
69    /**
70     * <pre>
71     * CTRL-EVENT-DISCONNECTED - Disconnect event - remove keys
72     * </pre>
73     */
74    private static final String disconnectedEvent = "DISCONNECTED";
75    /**
76     * <pre>
77     * CTRL-EVENT-STATE-CHANGE x
78     * </pre>
79     * <code>x</code> is the numerical value of the new state.
80     */
81    private static final String stateChangeEvent =  "STATE-CHANGE";
82    /**
83     * <pre>
84     * CTRL-EVENT-SCAN-RESULTS ready
85     * </pre>
86     */
87    private static final String scanResultsEvent =  "SCAN-RESULTS";
88
89    /**
90     * <pre>
91     * CTRL-EVENT-LINK-SPEED x Mb/s
92     * </pre>
93     * {@code x} is the link speed in Mb/sec.
94     */
95    private static final String linkSpeedEvent = "LINK-SPEED";
96    /**
97     * <pre>
98     * CTRL-EVENT-TERMINATING - signal x
99     * </pre>
100     * <code>x</code> is the signal that caused termination.
101     */
102    private static final String terminatingEvent =  "TERMINATING";
103    /**
104     * <pre>
105     * CTRL-EVENT-DRIVER-STATE state
106     * </pre>
107     * <code>state</code> is either STARTED or STOPPED
108     */
109    private static final String driverStateEvent = "DRIVER-STATE";
110
111    /**
112     * Regex pattern for extracting an Ethernet-style MAC address from a string.
113     * Matches a strings like the following:<pre>
114     * CTRL-EVENT-CONNECTED - Connection to 00:1e:58:ec:d5:6d completed (reauth) [id=1 id_str=]</pre>
115     */
116    private static Pattern mConnectedEventPattern =
117        Pattern.compile("((?:[0-9a-f]{2}:){5}[0-9a-f]{2}) .* \\[id=([0-9]+) ");
118
119    private final WifiStateMachine mWifiStateMachine;
120
121    /**
122     * This indicates the supplicant connection for the monitor is closed
123     */
124    private static final String monitorSocketClosed = "connection closed";
125
126    /**
127     * This indicates a read error on the monitor socket conenction
128     */
129    private static final String wpaRecvError = "recv error";
130
131    /**
132     * Tracks consecutive receive errors
133     */
134    private int mRecvErrors = 0;
135
136    /**
137     * Max errors before we close supplicant connection
138     */
139    private static final int MAX_RECV_ERRORS    = 10;
140
141    public WifiMonitor(WifiStateMachine wifiStateMachine) {
142        mWifiStateMachine = wifiStateMachine;
143    }
144
145    public void startMonitoring() {
146        new MonitorThread().start();
147    }
148
149    class MonitorThread extends Thread {
150        public MonitorThread() {
151            super("WifiMonitor");
152        }
153
154        public void run() {
155
156            if (connectToSupplicant()) {
157                // Send a message indicating that it is now possible to send commands
158                // to the supplicant
159                mWifiStateMachine.notifySupplicantConnection();
160            } else {
161                mWifiStateMachine.notifySupplicantLost();
162                return;
163            }
164
165            //noinspection InfiniteLoopStatement
166            for (;;) {
167                String eventStr = WifiNative.waitForEvent();
168
169                // Skip logging the common but mostly uninteresting scan-results event
170                if (Config.LOGD && eventStr.indexOf(scanResultsEvent) == -1) {
171                    Log.v(TAG, "Event [" + eventStr + "]");
172                }
173                if (!eventStr.startsWith(eventPrefix)) {
174                    if (eventStr.startsWith(wpaEventPrefix) &&
175                            0 < eventStr.indexOf(passwordKeyMayBeIncorrectEvent)) {
176                        handlePasswordKeyMayBeIncorrect();
177                    }
178                    continue;
179                }
180
181                String eventName = eventStr.substring(eventPrefixLen);
182                int nameEnd = eventName.indexOf(' ');
183                if (nameEnd != -1)
184                    eventName = eventName.substring(0, nameEnd);
185                if (eventName.length() == 0) {
186                    if (Config.LOGD) Log.i(TAG, "Received wpa_supplicant event with empty event name");
187                    continue;
188                }
189                /*
190                 * Map event name into event enum
191                 */
192                int event;
193                if (eventName.equals(connectedEvent))
194                    event = CONNECTED;
195                else if (eventName.equals(disconnectedEvent))
196                    event = DISCONNECTED;
197                else if (eventName.equals(stateChangeEvent))
198                    event = STATE_CHANGE;
199                else if (eventName.equals(scanResultsEvent))
200                    event = SCAN_RESULTS;
201                else if (eventName.equals(linkSpeedEvent))
202                    event = LINK_SPEED;
203                else if (eventName.equals(terminatingEvent))
204                    event = TERMINATING;
205                else if (eventName.equals(driverStateEvent)) {
206                    event = DRIVER_STATE;
207                }
208                else
209                    event = UNKNOWN;
210
211                String eventData = eventStr;
212                if (event == DRIVER_STATE || event == LINK_SPEED)
213                    eventData = eventData.split(" ")[1];
214                else if (event == STATE_CHANGE) {
215                    int ind = eventStr.indexOf(" ");
216                    if (ind != -1) {
217                        eventData = eventStr.substring(ind + 1);
218                    }
219                } else {
220                    int ind = eventStr.indexOf(" - ");
221                    if (ind != -1) {
222                        eventData = eventStr.substring(ind + 3);
223                    }
224                }
225
226                if (event == STATE_CHANGE) {
227                    handleSupplicantStateChange(eventData);
228                } else if (event == DRIVER_STATE) {
229                    handleDriverEvent(eventData);
230                } else if (event == TERMINATING) {
231                    /**
232                     * If monitor socket is closed, we have already
233                     * stopped the supplicant, simply exit the monitor thread
234                     */
235                    if (eventData.startsWith(monitorSocketClosed)) {
236                        if (Config.LOGD) {
237                            Log.d(TAG, "Monitor socket is closed, exiting thread");
238                        }
239                        break;
240                    }
241
242                    /**
243                     * Close the supplicant connection if we see
244                     * too many recv errors
245                     */
246                    if (eventData.startsWith(wpaRecvError)) {
247                        if (++mRecvErrors > MAX_RECV_ERRORS) {
248                            if (Config.LOGD) {
249                                Log.d(TAG, "too many recv errors, closing connection");
250                            }
251                        } else {
252                            continue;
253                        }
254                    }
255
256                    // notify and exit
257                    mWifiStateMachine.notifySupplicantLost();
258                    break;
259                } else {
260                    handleEvent(event, eventData);
261                }
262                mRecvErrors = 0;
263            }
264        }
265
266        private boolean connectToSupplicant() {
267            int connectTries = 0;
268
269            while (true) {
270                if (WifiNative.connectToSupplicant()) {
271                    return true;
272                }
273                if (connectTries++ < 3) {
274                    nap(5);
275                } else {
276                    break;
277                }
278            }
279            return false;
280        }
281
282        private void handlePasswordKeyMayBeIncorrect() {
283            mWifiStateMachine.notifyPasswordKeyMayBeIncorrect();
284        }
285
286        private void handleDriverEvent(String state) {
287            if (state == null) {
288                return;
289            }
290            if (state.equals("STOPPED")) {
291                mWifiStateMachine.notifyDriverStopped();
292            } else if (state.equals("STARTED")) {
293                mWifiStateMachine.notifyDriverStarted();
294            } else if (state.equals("HANGED")) {
295                mWifiStateMachine.notifyDriverHung();
296            }
297        }
298
299        /**
300         * Handle all supplicant events except STATE-CHANGE
301         * @param event the event type
302         * @param remainder the rest of the string following the
303         * event name and &quot;&#8195;&#8212;&#8195;&quot;
304         */
305        void handleEvent(int event, String remainder) {
306            switch (event) {
307                case DISCONNECTED:
308                    handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED, remainder);
309                    break;
310
311                case CONNECTED:
312                    handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED, remainder);
313                    break;
314
315                case SCAN_RESULTS:
316                    mWifiStateMachine.notifyScanResultsAvailable();
317                    break;
318
319                case UNKNOWN:
320                    break;
321            }
322        }
323
324        /**
325         * Handle the supplicant STATE-CHANGE event
326         * @param dataString New supplicant state string in the format:
327         * id=network-id state=new-state
328         */
329        private void handleSupplicantStateChange(String dataString) {
330            String[] dataTokens = dataString.split(" ");
331
332            String BSSID = null;
333            int networkId = -1;
334            int newState  = -1;
335            for (String token : dataTokens) {
336                String[] nameValue = token.split("=");
337                if (nameValue.length != 2) {
338                    continue;
339                }
340
341                if (nameValue[0].equals("BSSID")) {
342                    BSSID = nameValue[1];
343                    continue;
344                }
345
346                int value;
347                try {
348                    value = Integer.parseInt(nameValue[1]);
349                } catch (NumberFormatException e) {
350                    Log.w(TAG, "STATE-CHANGE non-integer parameter: " + token);
351                    continue;
352                }
353
354                if (nameValue[0].equals("id")) {
355                    networkId = value;
356                } else if (nameValue[0].equals("state")) {
357                    newState = value;
358                }
359            }
360
361            if (newState == -1) return;
362
363            SupplicantState newSupplicantState = SupplicantState.INVALID;
364            for (SupplicantState state : SupplicantState.values()) {
365                if (state.ordinal() == newState) {
366                    newSupplicantState = state;
367                    break;
368                }
369            }
370            if (newSupplicantState == SupplicantState.INVALID) {
371                Log.w(TAG, "Invalid supplicant state: " + newState);
372            }
373            mWifiStateMachine.notifySupplicantStateChange(networkId, BSSID, newSupplicantState);
374        }
375    }
376
377    private void handleNetworkStateChange(NetworkInfo.DetailedState newState, String data) {
378        String BSSID = null;
379        int networkId = -1;
380        if (newState == NetworkInfo.DetailedState.CONNECTED) {
381            Matcher match = mConnectedEventPattern.matcher(data);
382            if (!match.find()) {
383                if (Config.LOGD) Log.d(TAG, "Could not find BSSID in CONNECTED event string");
384            } else {
385                BSSID = match.group(1);
386                try {
387                    networkId = Integer.parseInt(match.group(2));
388                } catch (NumberFormatException e) {
389                    networkId = -1;
390                }
391            }
392        }
393        mWifiStateMachine.notifyNetworkStateChange(newState, BSSID, networkId);
394    }
395
396    /**
397     * Sleep for a period of time.
398     * @param secs the number of seconds to sleep
399     */
400    private static void nap(int secs) {
401        try {
402            Thread.sleep(secs * 1000);
403        } catch (InterruptedException ignore) {
404        }
405    }
406}
407