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""" 17A test that saves various networks and verifies the behavior of save, get, and 18remove through the ClientController API of WLAN policy. 19""" 20 21from acts import signals 22from acts.controllers.access_point import setup_ap 23from acts.controllers.ap_lib import hostapd_constants 24from acts.controllers.ap_lib import hostapd_security 25from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest 26from acts.utils import rand_ascii_str, rand_hex_str 27 28PSK_LEN = 64 29TIME_WAIT_FOR_DISCONNECT = 30 30TIME_WAIT_FOR_CONNECT = 30 31 32STATE_CONNECTED = "Connected" 33STATE_CONNECTING = "Connecting" 34CONNECTIONS_ENABLED = "ConnectionsEnabled" 35CONNECTIONS_DISABLED = "ConnectionsDisabled" 36SECURITY_NONE = "none" 37WEP = "wep" 38WPA = "wpa" 39WPA2 = "wpa2" 40WPA3 = "wpa3" 41CREDENTIAL_TYPE_NONE = "none" 42PASSWORD = "password" 43PSK = "psk" 44CREDENTIAL_VALUE_NONE = "" 45 46 47class SavedNetworksTest(WifiBaseTest): 48 """WLAN policy commands test class. 49 50 Test Bed Requirement: 51 * One or more Fuchsia devices 52 * One Access Point 53 """ 54 55 def setup_class(self): 56 super().setup_class() 57 # Keep track of whether we have started an access point in a test 58 if len(self.fuchsia_devices) < 1: 59 raise EnvironmentError("No Fuchsia devices found.") 60 for fd in self.fuchsia_devices: 61 fd.configure_wlan(association_mechanism='policy', 62 preserve_saved_networks=True) 63 64 def setup_test(self): 65 for fd in self.fuchsia_devices: 66 if not fd.wlan_policy_controller.remove_all_networks(): 67 raise EnvironmentError( 68 "Failed to remove all networks in setup") 69 self.access_points[0].stop_all_aps() 70 71 def teardown_class(self): 72 for fd in self.fuchsia_devices: 73 fd.wlan_policy_controller.remove_all_networks() 74 self.access_points[0].stop_all_aps() 75 76 def save_bad_network(self, fd, ssid, security_type, password=""): 77 """ Saves a network as specified on the given device and verify that we 78 Args: 79 fd: The Fuchsia device to save the network on 80 ssid: The SSID or name of the network to save. 81 security_type: The security type to save the network as, ie "none", 82 "wep", "wpa", "wpa2", or "wpa3" 83 password: The password to save for the network. Empty string represents 84 no password, and PSK should be provided as 64 character hex string. 85 """ 86 if fd.wlan_policy_controller.save_network(ssid, 87 security_type, 88 password=password): 89 self.log.info( 90 "Attempting to save bad network config %s did not give an error" 91 % ssid) 92 raise signals.TestFailure("Failed to get error saving bad network") 93 94 def check_get_saved_network(self, fd, ssid, security_type, credential_type, 95 credential_value): 96 """ Verify that get saved networks sees the single specified network. Used 97 for the tests that save and get a single network. Maps security types of 98 expected and actual to be case insensitive. 99 Args: 100 fd: Fuchsia device to run on. 101 ssid: The name of the network to check for. 102 security_type: The security of the network, ie "none", "wep", "wpa", 103 "wpa2", or "wpa3". 104 credential_type: The type of credential saved for the network, ie 105 "none", "password", or "psk". 106 credential_value: The actual credential, or "" if there is no credential. 107 """ 108 expected_networks = [{ 109 "ssid": ssid, 110 "security_type": security_type, 111 "credential_type": credential_type, 112 "credential_value": credential_value 113 }] 114 self.check_saved_networks(fd, expected_networks) 115 116 def check_saved_networks(self, fd, expected_networks): 117 """ Verify that the saved networks we get from the device match the provided 118 list of networks. 119 Args: 120 fd: The Fuchsia device to run on. 121 expected_networks: The list of networks we expect to get from the device, 122 unordered and in the same format as we would get: 123 [{"credential_type": _, "credential_value": _, 124 "security_type": _, "ssid": _}, ...] There should be 125 no duplicates in expected networks. 126 """ 127 actual_networks = list( 128 map(self.lower_case_network, 129 fd.wlan_policy_controller.get_saved_networks())) 130 expected_networks = list( 131 map(self.lower_case_network, 132 fd.wlan_policy_controller.get_saved_networks())) 133 134 if len(actual_networks) != len(expected_networks): 135 self.log.info( 136 "Number of expected saved networks does not match the actual number." 137 "Expected: %d, actual: %d" % 138 (len(actual_networks), len(expected_networks))) 139 raise signals.TestFailure( 140 "Failed to get the expected number of saved networks") 141 for network in actual_networks: 142 if network not in expected_networks: 143 self.log.info( 144 "Actual and expected networks do not match. Actual: %s,\n" 145 "Expected: %s" % (actual_networks, expected_networks)) 146 raise signals.TestFailure("Got an unexpected saved network") 147 148 def lower_case_network(self, network): 149 if "security_type" not in network: 150 self.log.error("Missing security type in network %s" % network) 151 raise signals.TestFailure("Network is missing security type") 152 if "credential_type" not in network: 153 self.log.error("Missing credential type in network %s" % network) 154 raise signals.TestFailure("Network is missing credential type") 155 {"ssid": network["ssid"], "security_type": network["security_type"]} 156 157 def save_and_check_network(self, ssid, security_type, password=""): 158 """ Perform a test for saving, getting, and removing a single network on each 159 device. 160 Args: 161 ssid: The network name to use. 162 security_type: The security of the network as a string, ie "none", 163 "wep", "wpa", "wpa2", or "wpa3" (case insensitive) 164 password: The password of the network. PSK should be given as 64 165 hexadecimal characters and none should be an empty string. 166 """ 167 for fd in self.fuchsia_devices: 168 if not fd.wlan_policy_controller.save_network( 169 ssid, security_type, password=password): 170 raise signals.TestFailure("Failed to save network") 171 self.check_get_saved_network(fd, ssid, security_type, 172 self.credentialType(password), 173 password) 174 175 def start_ap(self, ssid, security_type, password=None, hidden=False): 176 """ Starts an access point. 177 Args: 178 ssid: the SSID of the network to broadcast 179 security_type: the security type of the network to be broadcasted. This can be 180 None, "wep" "wpa", "wpa2", or "wpa3" (or from hostapd_constants.py) 181 password: the password to connect to the broadcasted network. The password is ignored 182 if security type is none. 183 """ 184 # Put together the security configuration of the network to be 185 # broadcasted. Open networks are represented by no security. 186 if security_type == None or security_type.upper() == SECURITY_NONE: 187 security = None 188 else: 189 security = hostapd_security.Security(security_mode=security_type, 190 password=password) 191 192 if len(self.access_points) > 0: 193 # Create an AP with default values other than the specified values. 194 setup_ap(self.access_points[0], 195 'whirlwind', 196 hostapd_constants.AP_DEFAULT_CHANNEL_5G, 197 ssid, 198 security=security) 199 200 else: 201 self.log.error( 202 "No access point available for test, please check config") 203 raise EnvironmentError("Failed to set up AP for test") 204 205 def credentialType(self, credentialValue): 206 """ Returns the type of the credential to compare against values reported """ 207 if len(credentialValue) == PSK_LEN: 208 return PSK 209 elif len(credentialValue) == 0: 210 return "none" 211 else: 212 return PASSWORD 213 214 def same_network_identifier(self, net_id, ssid, security_type): 215 """ Returns true if the network id is made of the given ssid and security 216 type, and false otherwise. Security type check is case insensitive. 217 """ 218 return net_id["ssid"] == ssid and net_id["type_"].upper( 219 ) == security_type.upper() 220 221 """Tests""" 222 223 def test_open_network_with_password(self): 224 for fd in self.fuchsia_devices: 225 # Save an open network with a password and verify that it fails to 226 # save. 227 self.save_bad_network(fd, rand_ascii_str(10), SECURITY_NONE, 228 rand_ascii_str(8)) 229 self.check_saved_networks(fd, {}) 230 231 def test_open_network(self): 232 ssid = rand_ascii_str(10) 233 self.save_and_check_network(ssid, SECURITY_NONE) 234 235 def test_network_with_psk(self): 236 ssid = rand_ascii_str(11) 237 # PSK are translated from hex to bytes when saved, and when returned 238 # by get_saved_networks it will be lower case. 239 psk = rand_hex_str(PSK_LEN).lower() 240 self.save_and_check_network(ssid, WPA2, psk) 241 242 def test_wep_network(self): 243 ssid = rand_ascii_str(12) 244 password = rand_ascii_str(13) 245 self.save_and_check_network(ssid, WEP, password) 246 247 def test_wpa2_network(self): 248 ssid = rand_ascii_str(9) 249 password = rand_ascii_str(15) 250 self.save_and_check_network(ssid, WPA2, password) 251 252 def test_wpa_network(self): 253 ssid = rand_ascii_str(16) 254 password = rand_ascii_str(9) 255 self.save_and_check_network(ssid, WPA, password) 256 257 def test_wpa3_network(self): 258 ssid = rand_ascii_str(9) 259 password = rand_ascii_str(15) 260 self.save_and_check_network(ssid, WPA3, password) 261 262 def test_save_network_persists(self): 263 ssid = rand_ascii_str(10) 264 security = WPA2 265 password = rand_ascii_str(10) 266 for fd in self.fuchsia_devices: 267 if not fd.wlan_policy_controller.save_network( 268 ssid, security, password=password): 269 raise signals.TestFailure("Failed to save network") 270 # Reboot the device. The network should be persistently saved 271 # before the command is completed. 272 fd.reboot() 273 self.check_get_saved_network(fd, ssid, security, PASSWORD, 274 password) 275 276 def test_same_ssid_diff_security(self): 277 for fd in self.fuchsia_devices: 278 saved_networks = fd.wlan_policy_controller.get_saved_networks() 279 ssid = rand_ascii_str(19) 280 password = rand_ascii_str(12) 281 if not fd.wlan_policy_controller.save_network( 282 ssid, WPA2, password=password): 283 raise signals.TestFailure("Failed to save network") 284 saved_networks.append({ 285 "ssid": ssid, 286 "security_type": WPA2, 287 "credential_type": PASSWORD, 288 "credential_value": password 289 }) 290 if not fd.wlan_policy_controller.save_network(ssid, SECURITY_NONE): 291 raise signals.TestFailure("Failed to save network") 292 saved_networks.append({ 293 "ssid": ssid, 294 "security_type": SECURITY_NONE, 295 "credential_type": CREDENTIAL_TYPE_NONE, 296 "credential_value": CREDENTIAL_VALUE_NONE 297 }) 298 actual_networks = fd.wlan_policy_controller.get_saved_networks() 299 # Both should be saved and present in network store since the have 300 # different security types and therefore different network identifiers. 301 self.check_saved_networks(fd, actual_networks) 302 303 def test_remove_disconnects(self): 304 # If we save, connect to, then remove the network while still connected 305 # to it, we expect the network will disconnect. This test requires a 306 # wpa2 network in the test config. Remove all other networks first so 307 # that we can't auto connect to them 308 ssid = rand_ascii_str(10) 309 security = WPA2 310 password = rand_ascii_str(10) 311 self.start_ap(ssid, security, password) 312 313 for fd in self.fuchsia_devices: 314 fd.wlan_policy_controller.wait_for_no_connections() 315 316 if not fd.wlan_policy_controller.save_and_connect: 317 raise signals.TestFailure( 318 "Failed to saved and connect to network") 319 320 if not fd.wlan_policy_controller.remove_all_networks_and_wait_for_no_connections( 321 ): 322 raise signals.TestFailure( 323 "Failed to disconnect from removed network") 324 325 def test_auto_connect_open(self): 326 # Start up AP with an open network with a random SSID 327 ssid = rand_ascii_str(10) 328 self.start_ap(ssid, None) 329 for fd in self.fuchsia_devices: 330 fd.wlan_policy_controller.wait_for_no_connections() 331 332 # Save the network and make sure that we see the device auto connect to it. 333 security = SECURITY_NONE 334 password = CREDENTIAL_VALUE_NONE 335 if not fd.wlan_policy_controller.save_network( 336 ssid, security, password=password): 337 raise signals.TestFailure("Failed to save network") 338 if not fd.wlan_policy_controller.wait_for_connect( 339 ssid, security, timeout=TIME_WAIT_FOR_CONNECT): 340 raise signals.TestFailure("Failed to connect to network") 341 342 def test_auto_connect_wpa3(self): 343 # Start up AP with an open network with a random SSID 344 ssid = rand_ascii_str(10) 345 security = WPA3 346 password = rand_ascii_str(10) 347 self.start_ap(ssid, security, password) 348 for fd in self.fuchsia_devices: 349 fd.wlan_policy_controller.wait_for_no_connections() 350 351 # Save the network and make sure that we see the device auto connect to it. 352 if not fd.wlan_policy_controller.save_network( 353 ssid, security, password=password): 354 raise signals.TestFailure("Failed to save network") 355 if not fd.wlan_policy_controller.wait_for_connect( 356 ssid, security, timeout=TIME_WAIT_FOR_CONNECT): 357 raise signals.TestFailure("Failed to connect to network") 358