• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3#   Copyright 2020 - The Android Open Source Project
4#
5#   Licensed under the Apache License, Version 2.0 (the "License");
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of the License at
8#
9#       http://www.apache.org/licenses/LICENSE-2.0
10#
11#   Unless required by applicable law or agreed to in writing, software
12#   distributed under the License is distributed on an "AS IS" BASIS,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16
17import requests
18import time
19
20from acts import logger
21from acts import signals
22
23SAVED_NETWORKS = "saved_networks"
24CLIENT_STATE = "client_connections_state"
25CONNECTIONS_ENABLED = "ConnectionsEnabled"
26CONNECTIONS_DISABLED = "ConnectionsDisabled"
27
28STATE_CONNECTED = 'Connected'
29STATE_CONNECTING = 'Connecting'
30STATE_DISCONNECTED = 'Disconnected'
31STATE_CONNECTION_STOPPED = 'ConnectionStopped'
32
33
34class WlanPolicyControllerError(signals.ControllerError):
35    pass
36
37
38class WlanPolicyController:
39    """Contains methods related to the wlan policy layer, to be used in the
40    FuchsiaDevice object.
41    """
42    def __init__(self, fuchsia_device):
43        self.device = fuchsia_device
44        self.log = logger.create_tagged_trace_logger(
45            'WlanPolicyController for FuchsiaDevice | %s' % self.device.ip)
46        self.client_controller = False
47        self.preserved_networks_and_client_state = None
48        self.policy_configured = False
49
50    def _configure_wlan(self, preserve_saved_networks, timeout=15):
51        """Sets up wlan policy layer.
52
53        Args:
54            preserve_saved_networks: bool, whether to clear existing saved
55                networks and client state, to be restored at test close.
56        """
57        end_time = time.time() + timeout
58
59        # Kill basemgr
60        while time.time() < end_time:
61            response = self.device.basemgr_lib.killBasemgr()
62            if not response.get('error'):
63                self.log.debug('Basemgr kill call successfully issued.')
64                break
65            self.log.debug(response['error'])
66            time.sleep(1)
67        else:
68            raise WlanPolicyControllerError(
69                'Failed to issue successful basemgr kill call.')
70
71        # Acquire control of policy layer
72        while time.time() < end_time:
73            # Create a client controller
74            response = self.device.wlan_policy_lib.wlanCreateClientController()
75            if response.get('error'):
76                self.log.debug(response['error'])
77                time.sleep(1)
78                continue
79            # Attempt to use the client controller (failure indicates a closed
80            # channel, meaning the client controller was rejected.
81            response = self.device.wlan_policy_lib.wlanGetSavedNetworks()
82            if response.get('error'):
83                self.log.debug(response['error'])
84                time.sleep(1)
85                continue
86            break
87        else:
88            raise WlanPolicyControllerError(
89                'Failed to create and use a WLAN policy client controller.')
90
91        self.log.info('ACTS tests now have control of the WLAN policy layer.')
92
93        if preserve_saved_networks and not self.preserved_networks_and_client_state:
94            self.preserved_networks_and_client_state = self.remove_and_preserve_networks_and_client_state(
95            )
96        if not self.start_client_connections():
97            raise WlanPolicyControllerError(
98                'Failed to start client connections during configuration.')
99
100        self.policy_configured = True
101
102    def _deconfigure_wlan(self):
103        if not self.stop_client_connections():
104            raise WlanPolicyControllerError(
105                'Failed to stop client connections during deconfiguration.')
106        self.policy_configured = False
107
108    def _clean_up(self):
109        if self.preserved_networks_and_client_state:
110            # It is possible for policy to have been configured before, but
111            # deconfigured before test end. In this case, in must be setup
112            # before restoring networks
113            if not self.policy_configured:
114                self._configure_wlan()
115            self.restore_preserved_networks_and_client_state()
116
117    def start_client_connections(self):
118        """Allow device to connect to networks via policy layer (including
119        autoconnecting to saved networks).
120
121        Returns:
122            True, if successful. False otherwise."""
123        start_response = self.device.wlan_policy_lib.wlanStartClientConnections(
124        )
125        if start_response.get('error'):
126            self.log.error('Failed to start client connections. Err: %s' %
127                           start_response['error'])
128            return False
129        return True
130
131    def stop_client_connections(self):
132        """Prevent device from connecting and autoconnecting to networks via the
133        policy layer.
134
135        Returns:
136            True, if successful. False otherwise."""
137        stop_response = self.device.wlan_policy_lib.wlanStopClientConnections()
138        if stop_response.get('error'):
139            self.log.error('Failed to stop client connections. Err: %s' %
140                           stop_response['error'])
141            return False
142        return True
143
144    def save_and_connect(self, ssid, security, password=None, timeout=30):
145        """ Saves and connects to the network. This is the policy version of
146        connect and check_connect_response because the policy layer
147        requires a saved network and the policy connect does not return
148        success or failure
149
150        Args:
151            ssid: string, the network name
152            security: string, security type of network (see wlan_policy_lib)
153            password: string, the credential of the network if applicable
154            timeout: int, time in seconds to wait for connection
155
156        Returns:
157            True, if successful. False otherwise.
158        """
159        # Save network and check response
160        if not self.save_network(ssid, security, password=password):
161            return False
162        # Make connect call and check response
163        self.device.wlan_policy_lib.wlanSetNewListener()
164        if not self.send_connect_command(ssid, security):
165            return False
166        return self.wait_for_connect(ssid, security, timeout=timeout)
167
168    def save_and_wait_for_autoconnect(self,
169                                      ssid,
170                                      security,
171                                      password=None,
172                                      timeout=30):
173        """Saves a network and waits, expecting an autoconnection to the newly
174        saved network. This differes from save_and_connect, as it doesn't
175        expressly trigger a connection first. There are cases in which an
176        autoconnect won't occur after a save (like if the device is connected
177        already), so this should be used with caution to test very specific
178        situations.
179
180        Args:
181            ssid: string, the network name
182            security: string, security type of network (see wlan_policy_lib)
183            password: string, the credential of the network if applicable
184            timeout: int, time in seconds to wait for connection
185
186        Returns:
187            True, if successful. False otherwise.
188        """
189        if not self.save_network(ssid, security, password=password):
190            return False
191        return self.wait_for_connect(ssid, security, timeout=timeout)
192
193    def remove_and_wait_for_disconnect(self,
194                                       ssid,
195                                       security_type,
196                                       password=None,
197                                       state=None,
198                                       status=None,
199                                       timeout=30):
200        """Removes a single network and waits for a disconnect. It is not
201        guaranteed the device will stay disconnected, as it may autoconnect
202        to a different saved network.
203
204        Args:
205            ssid: string, the network name
206            security: string, security type of network (see wlan_policy_lib)
207            password: string, the credential of the network if applicable
208            state: string, The connection state we are expecting, ie "Disconnected" or
209                "Failed"
210            status: string, The disconnect status we expect, it "ConnectionStopped" or
211                "ConnectionFailed"
212            timeout: int, time in seconds to wait for connection
213
214        Returns:
215            True, if successful. False otherwise.
216        """
217        self.device.wlan_policy_lib.wlanSetNewListener()
218        if not self.remove_network(ssid, security_type, password=password):
219            return False
220        return self.wait_for_disconnect(ssid,
221                                        security_type,
222                                        state=state,
223                                        status=status,
224                                        timeout=timeout)
225
226    def remove_all_networks_and_wait_for_no_connections(self, timeout=30):
227        """Removes all networks and waits until device is not connected to any
228        networks. This should be used as the policy version of disconnect.
229
230        Returns:
231            True, if successful. False otherwise.
232        """
233        self.device.wlan_policy_lib.wlanSetNewListener()
234        if not self.remove_all_networks():
235            self.log.error('Failed to remove all networks. Cannot continue to '
236                           'wait_for_no_connections.')
237            return False
238        return self.wait_for_no_connections(timeout=timeout)
239
240    def save_network(self, ssid, security_type, password=None):
241        """Save a network via the policy layer.
242
243        Args:
244            ssid: string, the network name
245            security: string, security type of network (see wlan_policy_lib)
246            password: string, the credential of the network if applicable
247
248        Returns:
249            True, if successful. False otherwise.
250        """
251        save_response = self.device.wlan_policy_lib.wlanSaveNetwork(
252            ssid, security_type, target_pwd=password)
253        if save_response.get('error'):
254            self.log.error('Failed to save network %s with error: %s' %
255                           (ssid, save_response['error']))
256            return False
257        return True
258
259    def remove_network(self, ssid, security_type, password=None):
260        """Remove a saved network via the policy layer.
261
262        Args:
263            ssid: string, the network name
264            security: string, security type of network (see wlan_policy_lib)
265            password: string, the credential of the network if applicable
266
267        Returns:
268            True, if successful. False otherwise.
269        """
270        remove_response = self.device.wlan_policy_lib.wlanRemoveNetwork(
271            ssid, security_type, target_pwd=password)
272        if remove_response.get('error'):
273            self.log.error('Failed to remove network %s with error: %s' %
274                           (ssid, remove_response['error']))
275            return False
276        return True
277
278    def remove_all_networks(self):
279        """Removes all saved networks from device.
280
281        Returns:
282            True, if successful. False otherwise.
283        """
284        remove_all_response = self.device.wlan_policy_lib.wlanRemoveAllNetworks(
285        )
286        if remove_all_response.get('error'):
287            self.log.error('Error occurred removing all networks: %s' %
288                           remove_all_response['error'])
289            return False
290        return True
291
292    def get_saved_networks(self):
293        """Retrieves saved networks from device.
294
295        Returns:
296            list of saved networks
297
298        Raises:
299            WlanPolicyControllerError, if retrieval fails.
300        """
301        saved_networks_response = self.device.wlan_policy_lib.wlanGetSavedNetworks(
302        )
303        if saved_networks_response.get('error'):
304            raise WlanPolicyControllerError(
305                'Failed to retrieve saved networks: %s' %
306                saved_networks_response['error'])
307        return saved_networks_response['result']
308
309    def send_connect_command(self, ssid, security_type):
310        """Sends a connect command to a network that is already saved. This does
311        not wait to guarantee the connection is successful (for that, use
312        save_and_connect).
313
314        Args:
315            ssid: string, the network name
316            security: string, security type of network (see wlan_policy_lib)
317            password: string, the credential of the network if applicable
318
319        Returns:
320            True, if command send successfully. False otherwise.
321        """
322        connect_response = self.device.wlan_policy_lib.wlanConnect(
323            ssid, security_type)
324        if connect_response.get('error'):
325            self.log.error(
326                'Error occurred when sending policy connect command: %s' %
327                connect_response['error'])
328            return False
329        return True
330
331    def wait_for_connect(self, ssid, security_type, timeout=30):
332        """ Wait until the device has connected to the specified network.
333        Args:
334            ssid: string, the network name
335            security: string, security type of network (see wlan_policy_lib)
336            timeout: int, seconds to wait for a update showing connection
337        Returns:
338            True if we see a connect to the network, False otherwise.
339        """
340        security_type = str(security_type)
341        # Wait until we've connected.
342        end_time = time.time() + timeout
343        while time.time() < end_time:
344            time_left = max(1, int(end_time - time.time()))
345
346            try:
347                update = self.device.wlan_policy_lib.wlanGetUpdate(
348                    timeout=time_left)
349            except requests.exceptions.Timeout:
350                self.log.error('Timed out waiting for response from device '
351                               'while waiting for network with SSID "%s" to '
352                               'connect. Device took too long to connect or '
353                               'the request timed out for another reason.' %
354                               ssid)
355                self.device.wlan_policy_lib.wlanSetNewListener()
356                return False
357            if update.get('error'):
358                # This can occur for many reasons, so it is not necessarily a
359                # failure.
360                self.log.debug('Error occurred getting status update: %s' %
361                               update['error'])
362                continue
363
364            for network in update['result']['networks']:
365                if network['id']['ssid'] == ssid or network['id'][
366                        'type_'].lower() == security_type.lower():
367                    if 'state' not in network:
368                        raise WlanPolicyControllerError(
369                            'WLAN status missing state field.')
370                    elif network['state'].lower() == STATE_CONNECTED.lower():
371                        return True
372            # Wait a bit before requesting another status update
373            time.sleep(1)
374        # Stopped getting updates because out timeout
375        self.log.error('Timed out waiting for network with SSID "%s" to '
376                       "connect" % ssid)
377        return False
378
379    def wait_for_disconnect(self,
380                            ssid,
381                            security_type,
382                            state=None,
383                            status=None,
384                            timeout=30):
385        """ Wait for a disconnect of the specified network on the given device. This
386        will check that the correct connection state and disconnect status are
387        given in update. If we do not see a disconnect after some time,
388        return false.
389
390        Args:
391            ssid: string, the network name
392            security: string, security type of network (see wlan_policy_lib)
393            state: string, The connection state we are expecting, ie "Disconnected" or
394                "Failed"
395            status: string, The disconnect status we expect, it "ConnectionStopped" or
396                "ConnectionFailed"
397            timeout: int, seconds to wait before giving up
398
399        Returns: True if we saw a disconnect as specified, or False otherwise.
400        """
401        if not state:
402            state = STATE_DISCONNECTED
403        if not status:
404            status = STATE_CONNECTION_STOPPED
405
406        end_time = time.time() + timeout
407        while time.time() < end_time:
408            time_left = max(1, int(end_time - time.time()))
409            try:
410                update = self.device.wlan_policy_lib.wlanGetUpdate(
411                    timeout=time_left)
412            except requests.exceptions.Timeout:
413                self.log.error(
414                    'Timed out waiting for response from device '
415                    'while waiting for network with SSID "%s" to '
416                    'disconnect. Device took too long to disconnect '
417                    'or the request timed out for another reason.' % ssid)
418                self.device.wlan_policy_lib.wlanSetNewListener()
419                return False
420
421            if update.get('error'):
422                # This can occur for many reasons, so it is not necessarily a
423                # failure.
424                self.log.debug('Error occurred getting status update: %s' %
425                               update['error'])
426                continue
427            # Update should include network, either connected to or recently disconnected.
428            if len(update['result']['networks']) == 0:
429                raise WlanPolicyControllerError(
430                    'WLAN state update is missing network.')
431
432            for network in update['result']['networks']:
433                if network['id']['ssid'] == ssid or network['id'][
434                        'type_'].lower() == security_type.lower():
435                    if 'state' not in network or 'status' not in network:
436                        raise WlanPolicyControllerError(
437                            'Client state summary\'s network is missing fields'
438                        )
439                    # If still connected, we will wait for another update and check again
440                    elif network['state'].lower() == STATE_CONNECTED.lower():
441                        continue
442                    elif network['state'].lower() == STATE_CONNECTING.lower():
443                        self.log.error(
444                            'Update is "Connecting", but device should already be '
445                            'connected; expected disconnect')
446                        return False
447                    # Check that the network state and disconnect status are expected, ie
448                    # that it isn't ConnectionFailed when we expect ConnectionStopped
449                    elif network['state'].lower() != state.lower(
450                    ) or network['status'].lower() != status.lower():
451                        self.log.error(
452                            'Connection failed: a network failure occurred that is unrelated'
453                            'to remove network or incorrect status update. \nExpected state: '
454                            '%s, Status: %s,\nActual update: %s' %
455                            (state, status, network))
456                        return False
457                    else:
458                        return True
459            # Wait a bit before requesting another status update
460            time.sleep(1)
461        # Stopped getting updates because out timeout
462        self.log.error('Timed out waiting for network with SSID "%s" to '
463                       'connect' % ssid)
464        return False
465
466    def wait_for_no_connections(self, timeout=30):
467        """ Waits to see that there are no existing connections the device. This
468        is the simplest way to watch for disconnections when only a single
469        network is saved/present.
470
471        Args:
472            timeout: int, time in seconds to wait to see no connections
473
474        Returns:
475            True, if successful. False, if still connected after timeout.
476        """
477        end_time = time.time() + timeout
478        while time.time() < end_time:
479            time_left = max(1, int(end_time - time.time()))
480            try:
481                update = self.device.wlan_policy_lib.wlanGetUpdate(
482                    timeout=time_left)
483            except requests.exceptions.Timeout:
484                self.log.info(
485                    "Timed out getting status update while waiting for all"
486                    " connections to end.")
487                self.device.wlan_policy_lib.wlanSetNewListener()
488                return False
489
490            if update["error"] != None:
491                self.log.info("Failed to get status update")
492                return False
493            # If any network is connected or being connected to, wait for them
494            # to disconnect.
495            if any(network['state'].lower() in
496                   {STATE_CONNECTED.lower(),
497                    STATE_CONNECTING.lower()}
498                   for network in update['result']['networks']):
499                continue
500            else:
501                return True
502        return False
503
504    def remove_and_preserve_networks_and_client_state(self):
505        """ Preserves networks already saved on devices before removing them to
506        setup up for a clean test environment. Records the state of client
507        connections before tests.
508
509        Raises:
510            WlanPolicyControllerError, if the network removal is unsuccessful
511        """
512        # Save preexisting saved networks
513        preserved_networks_and_state = {}
514        saved_networks_response = self.device.wlan_policy_lib.wlanGetSavedNetworks(
515        )
516        if saved_networks_response.get('error'):
517            raise WlanPolicyControllerError(
518                'Failed to get preexisting saved networks: %s' %
519                saved_networks_response['error'])
520        if saved_networks_response.get('result') != None:
521            preserved_networks_and_state[
522                SAVED_NETWORKS] = saved_networks_response['result']
523
524        # Remove preexisting saved networks
525        if not self.remove_all_networks():
526            raise WlanPolicyControllerError(
527                'Failed to clear networks and disconnect at FuchsiaDevice creation.'
528            )
529
530        self.device.wlan_policy_lib.wlanSetNewListener()
531        update_response = self.device.wlan_policy_lib.wlanGetUpdate()
532        update_result = update_response.get('result', {})
533        if update_result.get('state'):
534            preserved_networks_and_state[CLIENT_STATE] = update_result['state']
535        else:
536            self.log.warn('Failed to get update; test will not start or '
537                          'stop client connections at the end of the test.')
538
539        self.log.info('Saved networks cleared and preserved.')
540        return preserved_networks_and_state
541
542    def restore_preserved_networks_and_client_state(self):
543        """ Restore saved networks and client state onto device if they have
544        been preserved.
545        """
546        if not self.remove_all_networks():
547            self.log.warn('Failed to remove saved networks before restore.')
548        restore_success = True
549        for network in self.preserved_networks_and_client_state[
550                SAVED_NETWORKS]:
551            if not self.save_network(network["ssid"], network["security_type"],
552                                     network["credential_value"]):
553                self.log.warn('Failed to restore network (%s).' %
554                              network['ssid'])
555                restore_success = False
556        starting_state = self.preserved_networks_and_client_state[CLIENT_STATE]
557        if starting_state == CONNECTIONS_ENABLED:
558            state_restored = self.start_client_connections()
559        else:
560            state_restored = self.stop_client_connections()
561        if not state_restored:
562            self.log.warn('Failed to restore client connections state.')
563            restore_success = False
564        if restore_success:
565            self.log.info('Preserved networks and client state restored.')
566            self.preserved_networks_and_client_state = None
567        return restore_success
568