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