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