| #!/usr/bin/env python3 |
| # |
| # Copyright 2022 The Fuchsia Authors |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import logging |
| import os |
| import shutil |
| import time |
| from enum import IntEnum |
| from queue import Empty |
| |
| from mobly import asserts |
| |
| from antlion import context, signals, utils |
| from antlion.controllers.ap_lib.hostapd_constants import BAND_2G, BAND_5G |
| from antlion.test_utils.wifi import wifi_constants |
| |
| # Default timeout used for reboot, toggle WiFi and Airplane mode, |
| # for the system to settle down after the operation. |
| DEFAULT_TIMEOUT = 10 |
| # Number of seconds to wait for events that are supposed to happen quickly. |
| # Like onSuccess for start background scan and confirmation on wifi state |
| # change. |
| SHORT_TIMEOUT = 30 |
| ROAMING_TIMEOUT = 30 |
| WIFI_CONNECTION_TIMEOUT_DEFAULT = 30 |
| DEFAULT_SCAN_TRIES = 3 |
| DEFAULT_CONNECT_TRIES = 3 |
| # Speed of light in m/s. |
| SPEED_OF_LIGHT = 299792458 |
| |
| DEFAULT_PING_ADDR = "https://www.google.com/robots.txt" |
| |
| CNSS_DIAG_CONFIG_PATH = "/data/vendor/wifi/cnss_diag/" |
| CNSS_DIAG_CONFIG_FILE = "cnss_diag.conf" |
| |
| ROAMING_ATTN = { |
| "AP1_on_AP2_off": [0, 0, 95, 95], |
| "AP1_off_AP2_on": [95, 95, 0, 0], |
| "default": [0, 0, 0, 0], |
| } |
| |
| |
| class WifiEnums: |
| SSID_KEY = "SSID" # Used for Wifi & SoftAp |
| SSID_PATTERN_KEY = "ssidPattern" |
| NETID_KEY = "network_id" |
| BSSID_KEY = "BSSID" # Used for Wifi & SoftAp |
| BSSID_PATTERN_KEY = "bssidPattern" |
| PWD_KEY = "password" # Used for Wifi & SoftAp |
| frequency_key = "frequency" |
| HIDDEN_KEY = "hiddenSSID" # Used for Wifi & SoftAp |
| IS_APP_INTERACTION_REQUIRED = "isAppInteractionRequired" |
| IS_USER_INTERACTION_REQUIRED = "isUserInteractionRequired" |
| IS_SUGGESTION_METERED = "isMetered" |
| PRIORITY = "priority" |
| SECURITY = "security" # Used for Wifi & SoftAp |
| |
| # Used for SoftAp |
| AP_BAND_KEY = "apBand" |
| AP_CHANNEL_KEY = "apChannel" |
| AP_BANDS_KEY = "apBands" |
| AP_CHANNEL_FREQUENCYS_KEY = "apChannelFrequencies" |
| AP_MAC_RANDOMIZATION_SETTING_KEY = "MacRandomizationSetting" |
| AP_BRIDGED_OPPORTUNISTIC_SHUTDOWN_ENABLE_KEY = ( |
| "BridgedModeOpportunisticShutdownEnabled" |
| ) |
| AP_IEEE80211AX_ENABLED_KEY = "Ieee80211axEnabled" |
| AP_MAXCLIENTS_KEY = "MaxNumberOfClients" |
| AP_SHUTDOWNTIMEOUT_KEY = "ShutdownTimeoutMillis" |
| AP_SHUTDOWNTIMEOUTENABLE_KEY = "AutoShutdownEnabled" |
| AP_CLIENTCONTROL_KEY = "ClientControlByUserEnabled" |
| AP_ALLOWEDLIST_KEY = "AllowedClientList" |
| AP_BLOCKEDLIST_KEY = "BlockedClientList" |
| |
| WIFI_CONFIG_SOFTAP_BAND_2G = 1 |
| WIFI_CONFIG_SOFTAP_BAND_5G = 2 |
| WIFI_CONFIG_SOFTAP_BAND_2G_5G = 3 |
| WIFI_CONFIG_SOFTAP_BAND_6G = 4 |
| WIFI_CONFIG_SOFTAP_BAND_2G_6G = 5 |
| WIFI_CONFIG_SOFTAP_BAND_5G_6G = 6 |
| WIFI_CONFIG_SOFTAP_BAND_ANY = 7 |
| |
| # DO NOT USE IT for new test case! Replaced by WIFI_CONFIG_SOFTAP_BAND_ |
| WIFI_CONFIG_APBAND_2G = WIFI_CONFIG_SOFTAP_BAND_2G |
| WIFI_CONFIG_APBAND_5G = WIFI_CONFIG_SOFTAP_BAND_5G |
| WIFI_CONFIG_APBAND_AUTO = WIFI_CONFIG_SOFTAP_BAND_2G_5G |
| |
| WIFI_CONFIG_APBAND_2G_OLD = 0 |
| WIFI_CONFIG_APBAND_5G_OLD = 1 |
| WIFI_CONFIG_APBAND_AUTO_OLD = -1 |
| |
| WIFI_WPS_INFO_PBC = 0 |
| WIFI_WPS_INFO_DISPLAY = 1 |
| WIFI_WPS_INFO_KEYPAD = 2 |
| WIFI_WPS_INFO_LABEL = 3 |
| WIFI_WPS_INFO_INVALID = 4 |
| |
| class CountryCode: |
| AUSTRALIA = "AU" |
| CHINA = "CN" |
| GERMANY = "DE" |
| JAPAN = "JP" |
| UK = "GB" |
| US = "US" |
| UNKNOWN = "UNKNOWN" |
| |
| # Start of Macros for EAP |
| # EAP types |
| class Eap(IntEnum): |
| NONE = -1 |
| PEAP = 0 |
| TLS = 1 |
| TTLS = 2 |
| PWD = 3 |
| SIM = 4 |
| AKA = 5 |
| AKA_PRIME = 6 |
| UNAUTH_TLS = 7 |
| |
| # EAP Phase2 types |
| class EapPhase2(IntEnum): |
| NONE = 0 |
| PAP = 1 |
| MSCHAP = 2 |
| MSCHAPV2 = 3 |
| GTC = 4 |
| |
| class Enterprise: |
| # Enterprise Config Macros |
| EMPTY_VALUE = "NULL" |
| EAP = "eap" |
| PHASE2 = "phase2" |
| IDENTITY = "identity" |
| ANON_IDENTITY = "anonymous_identity" |
| PASSWORD = "password" |
| SUBJECT_MATCH = "subject_match" |
| ALTSUBJECT_MATCH = "altsubject_match" |
| DOM_SUFFIX_MATCH = "domain_suffix_match" |
| CLIENT_CERT = "client_cert" |
| CA_CERT = "ca_cert" |
| ENGINE = "engine" |
| ENGINE_ID = "engine_id" |
| PRIVATE_KEY_ID = "key_id" |
| REALM = "realm" |
| PLMN = "plmn" |
| FQDN = "FQDN" |
| FRIENDLY_NAME = "providerFriendlyName" |
| ROAMING_IDS = "roamingConsortiumIds" |
| OCSP = "ocsp" |
| |
| # End of Macros for EAP |
| |
| # Macros as specified in the WifiScanner code. |
| WIFI_BAND_UNSPECIFIED = 0 # not specified |
| WIFI_BAND_24_GHZ = 1 # 2.4 GHz band |
| WIFI_BAND_5_GHZ = 2 # 5 GHz band without DFS channels |
| WIFI_BAND_5_GHZ_DFS_ONLY = 4 # 5 GHz band with DFS channels |
| WIFI_BAND_5_GHZ_WITH_DFS = 6 # 5 GHz band with DFS channels |
| WIFI_BAND_BOTH = 3 # both bands without DFS channels |
| WIFI_BAND_BOTH_WITH_DFS = 7 # both bands with DFS channels |
| |
| SCAN_TYPE_LOW_LATENCY = 0 |
| SCAN_TYPE_LOW_POWER = 1 |
| SCAN_TYPE_HIGH_ACCURACY = 2 |
| |
| # US Wifi frequencies |
| ALL_2G_FREQUENCIES = [ |
| 2412, |
| 2417, |
| 2422, |
| 2427, |
| 2432, |
| 2437, |
| 2442, |
| 2447, |
| 2452, |
| 2457, |
| 2462, |
| ] |
| DFS_5G_FREQUENCIES = [ |
| 5260, |
| 5280, |
| 5300, |
| 5320, |
| 5500, |
| 5520, |
| 5540, |
| 5560, |
| 5580, |
| 5600, |
| 5620, |
| 5640, |
| 5660, |
| 5680, |
| 5700, |
| 5720, |
| ] |
| NONE_DFS_5G_FREQUENCIES = [5180, 5200, 5220, 5240, 5745, 5765, 5785, 5805, 5825] |
| ALL_5G_FREQUENCIES = DFS_5G_FREQUENCIES + NONE_DFS_5G_FREQUENCIES |
| |
| band_to_frequencies = { |
| WIFI_BAND_24_GHZ: ALL_2G_FREQUENCIES, |
| WIFI_BAND_5_GHZ: NONE_DFS_5G_FREQUENCIES, |
| WIFI_BAND_5_GHZ_DFS_ONLY: DFS_5G_FREQUENCIES, |
| WIFI_BAND_5_GHZ_WITH_DFS: ALL_5G_FREQUENCIES, |
| WIFI_BAND_BOTH: ALL_2G_FREQUENCIES + NONE_DFS_5G_FREQUENCIES, |
| WIFI_BAND_BOTH_WITH_DFS: ALL_5G_FREQUENCIES + ALL_2G_FREQUENCIES, |
| } |
| |
| # TODO: add all of the band mapping. |
| softap_band_frequencies = { |
| WIFI_CONFIG_SOFTAP_BAND_2G: ALL_2G_FREQUENCIES, |
| WIFI_CONFIG_SOFTAP_BAND_5G: ALL_5G_FREQUENCIES, |
| } |
| |
| # All Wifi frequencies to channels lookup. |
| freq_to_channel = { |
| 2412: 1, |
| 2417: 2, |
| 2422: 3, |
| 2427: 4, |
| 2432: 5, |
| 2437: 6, |
| 2442: 7, |
| 2447: 8, |
| 2452: 9, |
| 2457: 10, |
| 2462: 11, |
| 2467: 12, |
| 2472: 13, |
| 2484: 14, |
| 4915: 183, |
| 4920: 184, |
| 4925: 185, |
| 4935: 187, |
| 4940: 188, |
| 4945: 189, |
| 4960: 192, |
| 4980: 196, |
| 5035: 7, |
| 5040: 8, |
| 5045: 9, |
| 5055: 11, |
| 5060: 12, |
| 5080: 16, |
| 5170: 34, |
| 5180: 36, |
| 5190: 38, |
| 5200: 40, |
| 5210: 42, |
| 5220: 44, |
| 5230: 46, |
| 5240: 48, |
| 5260: 52, |
| 5280: 56, |
| 5300: 60, |
| 5320: 64, |
| 5500: 100, |
| 5520: 104, |
| 5540: 108, |
| 5560: 112, |
| 5580: 116, |
| 5600: 120, |
| 5620: 124, |
| 5640: 128, |
| 5660: 132, |
| 5680: 136, |
| 5700: 140, |
| 5745: 149, |
| 5765: 153, |
| 5785: 157, |
| 5795: 159, |
| 5805: 161, |
| 5825: 165, |
| } |
| |
| # All Wifi channels to frequencies lookup. |
| channel_2G_to_freq = { |
| 1: 2412, |
| 2: 2417, |
| 3: 2422, |
| 4: 2427, |
| 5: 2432, |
| 6: 2437, |
| 7: 2442, |
| 8: 2447, |
| 9: 2452, |
| 10: 2457, |
| 11: 2462, |
| 12: 2467, |
| 13: 2472, |
| 14: 2484, |
| } |
| |
| channel_5G_to_freq = { |
| 183: 4915, |
| 184: 4920, |
| 185: 4925, |
| 187: 4935, |
| 188: 4940, |
| 189: 4945, |
| 192: 4960, |
| 196: 4980, |
| 7: 5035, |
| 8: 5040, |
| 9: 5045, |
| 11: 5055, |
| 12: 5060, |
| 16: 5080, |
| 34: 5170, |
| 36: 5180, |
| 38: 5190, |
| 40: 5200, |
| 42: 5210, |
| 44: 5220, |
| 46: 5230, |
| 48: 5240, |
| 50: 5250, |
| 52: 5260, |
| 56: 5280, |
| 60: 5300, |
| 64: 5320, |
| 100: 5500, |
| 104: 5520, |
| 108: 5540, |
| 112: 5560, |
| 116: 5580, |
| 120: 5600, |
| 124: 5620, |
| 128: 5640, |
| 132: 5660, |
| 136: 5680, |
| 140: 5700, |
| 149: 5745, |
| 151: 5755, |
| 153: 5765, |
| 155: 5775, |
| 157: 5785, |
| 159: 5795, |
| 161: 5805, |
| 165: 5825, |
| } |
| |
| channel_6G_to_freq = {4 * x + 1: 5955 + 20 * x for x in range(59)} |
| |
| channel_to_freq = { |
| "2G": channel_2G_to_freq, |
| "5G": channel_5G_to_freq, |
| "6G": channel_6G_to_freq, |
| } |
| |
| |
| def _assert_on_fail_handler(func, assert_on_fail, *args, **kwargs): |
| """Wrapper function that handles the bahevior of assert_on_fail. |
| |
| When assert_on_fail is True, let all test signals through, which can |
| terminate test cases directly. When assert_on_fail is False, the wrapper |
| raises no test signals and reports operation status by returning True or |
| False. |
| |
| Args: |
| func: The function to wrap. This function reports operation status by |
| raising test signals. |
| assert_on_fail: A boolean that specifies if the output of the wrapper |
| is test signal based or return value based. |
| args: Positional args for func. |
| kwargs: Name args for func. |
| |
| Returns: |
| If assert_on_fail is True, returns True/False to signal operation |
| status, otherwise return nothing. |
| """ |
| try: |
| func(*args, **kwargs) |
| if not assert_on_fail: |
| return True |
| except signals.TestSignal: |
| if assert_on_fail: |
| raise |
| return False |
| |
| |
| def match_networks(target_params, networks): |
| """Finds the WiFi networks that match a given set of parameters in a list |
| of WiFi networks. |
| |
| To be considered a match, the network should contain every key-value pair |
| of target_params |
| |
| Args: |
| target_params: A dict with 1 or more key-value pairs representing a Wi-Fi network. |
| E.g { 'SSID': 'wh_ap1_5g', 'BSSID': '30:b5:c2:33:e4:47' } |
| networks: A list of dict objects representing WiFi networks. |
| |
| Returns: |
| The networks that match the target parameters. |
| """ |
| results = [] |
| asserts.assert_true( |
| target_params, "Expected networks object 'target_params' is empty" |
| ) |
| for n in networks: |
| add_network = 1 |
| for k, v in target_params.items(): |
| if k not in n: |
| add_network = 0 |
| break |
| if n[k] != v: |
| add_network = 0 |
| break |
| if add_network: |
| results.append(n) |
| return results |
| |
| |
| def wifi_toggle_state(ad, new_state=None, assert_on_fail=True): |
| """Toggles the state of wifi. |
| |
| Args: |
| ad: An AndroidDevice object. |
| new_state: Wifi state to set to. If None, opposite of the current state. |
| assert_on_fail: If True, error checks in this function will raise test |
| failure signals. |
| |
| Returns: |
| If assert_on_fail is False, function returns True if the toggle was |
| successful, False otherwise. If assert_on_fail is True, no return value. |
| """ |
| return _assert_on_fail_handler( |
| _wifi_toggle_state, assert_on_fail, ad, new_state=new_state |
| ) |
| |
| |
| def _wifi_toggle_state(ad, new_state=None): |
| """Toggles the state of wifi. |
| |
| TestFailure signals are raised when something goes wrong. |
| |
| Args: |
| ad: An AndroidDevice object. |
| new_state: The state to set Wi-Fi to. If None, opposite of the current |
| state will be set. |
| """ |
| if new_state is None: |
| new_state = not ad.droid.wifiCheckState() |
| elif new_state == ad.droid.wifiCheckState(): |
| # Check if the new_state is already achieved, so we don't wait for the |
| # state change event by mistake. |
| return |
| ad.droid.wifiStartTrackingStateChange() |
| ad.log.info("Setting Wi-Fi state to %s.", new_state) |
| ad.ed.clear_all_events() |
| # Setting wifi state. |
| ad.droid.wifiToggleState(new_state) |
| time.sleep(2) |
| fail_msg = f"Failed to set Wi-Fi state to {new_state} on {ad.serial}." |
| try: |
| ad.ed.wait_for_event( |
| wifi_constants.WIFI_STATE_CHANGED, |
| lambda x: x["data"]["enabled"] == new_state, |
| SHORT_TIMEOUT, |
| ) |
| except Empty: |
| asserts.assert_equal(new_state, ad.droid.wifiCheckState(), fail_msg) |
| finally: |
| ad.droid.wifiStopTrackingStateChange() |
| |
| |
| def reset_wifi(ad): |
| """Clears all saved Wi-Fi networks on a device. |
| |
| This will turn Wi-Fi on. |
| |
| Args: |
| ad: An AndroidDevice object. |
| |
| """ |
| networks = ad.droid.wifiGetConfiguredNetworks() |
| if not networks: |
| return |
| removed = [] |
| for n in networks: |
| if n["networkId"] not in removed: |
| ad.droid.wifiForgetNetwork(n["networkId"]) |
| removed.append(n["networkId"]) |
| else: |
| continue |
| try: |
| event = ad.ed.pop_event( |
| wifi_constants.WIFI_FORGET_NW_SUCCESS, SHORT_TIMEOUT |
| ) |
| except Empty: |
| logging.warning("Could not confirm the removal of network %s.", n) |
| # Check again to see if there's any network left. |
| asserts.assert_true( |
| not ad.droid.wifiGetConfiguredNetworks(), |
| f"Failed to remove these configured Wi-Fi networks: {networks}", |
| ) |
| |
| |
| def wifi_test_device_init(ad, country_code=WifiEnums.CountryCode.US): |
| """Initializes an android device for wifi testing. |
| |
| 0. Make sure SL4A connection is established on the android device. |
| 1. Disable location service's WiFi scan. |
| 2. Turn WiFi on. |
| 3. Clear all saved networks. |
| 4. Set country code to US. |
| 5. Enable WiFi verbose logging. |
| 6. Sync device time with computer time. |
| 7. Turn off cellular data. |
| 8. Turn off ambient display. |
| """ |
| utils.require_sl4a((ad,)) |
| ad.droid.wifiScannerToggleAlwaysAvailable(False) |
| msg = "Failed to turn off location service's scan." |
| asserts.assert_true(not ad.droid.wifiScannerIsAlwaysAvailable(), msg) |
| wifi_toggle_state(ad, True) |
| reset_wifi(ad) |
| ad.droid.wifiEnableVerboseLogging(1) |
| msg = "Failed to enable WiFi verbose logging." |
| asserts.assert_equal(ad.droid.wifiGetVerboseLoggingLevel(), 1, msg) |
| # We don't verify the following settings since they are not critical. |
| # Set wpa_supplicant log level to EXCESSIVE. |
| output = ad.adb.shell( |
| "wpa_cli -i wlan0 -p -g@android:wpa_wlan0 IFNAME=" "wlan0 log_level EXCESSIVE", |
| ignore_status=True, |
| ) |
| ad.log.info("wpa_supplicant log change status: %s", output) |
| utils.sync_device_time(ad) |
| ad.droid.telephonyToggleDataConnection(False) |
| set_wifi_country_code(ad, country_code) |
| utils.set_ambient_display(ad, False) |
| |
| |
| def set_wifi_country_code(ad, country_code): |
| """Sets the wifi country code on the device. |
| |
| Args: |
| ad: An AndroidDevice object. |
| country_code: 2 letter ISO country code |
| |
| Raises: |
| An RpcException if unable to set the country code. |
| """ |
| try: |
| ad.adb.shell(f"cmd wifi force-country-code enabled {country_code}") |
| except Exception as e: |
| ad.log.warn( |
| f"Failed to set country code to {country_code}; defaulting to US. Error: {e}" |
| ) |
| ad.droid.wifiSetCountryCode(WifiEnums.CountryCode.US) |
| |
| |
| def start_wifi_connection_scan_and_return_status(ad): |
| """ |
| Starts a wifi connection scan and wait for results to become available |
| or a scan failure to be reported. |
| |
| Args: |
| ad: An AndroidDevice object. |
| Returns: |
| True: if scan succeeded & results are available |
| False: if scan failed |
| """ |
| ad.ed.clear_all_events() |
| ad.droid.wifiStartScan() |
| try: |
| events = ad.ed.pop_events("WifiManagerScan(ResultsAvailable|Failure)", 60) |
| except Empty: |
| asserts.fail("Wi-Fi scan results/failure did not become available within 60s.") |
| # If there are multiple matches, we check for atleast one success. |
| for event in events: |
| if event["name"] == "WifiManagerScanResultsAvailable": |
| return True |
| elif event["name"] == "WifiManagerScanFailure": |
| ad.log.debug("Scan failure received") |
| return False |
| |
| |
| def start_wifi_connection_scan_and_check_for_network(ad, network_ssid, max_tries=3): |
| """ |
| Start connectivity scans & checks if the |network_ssid| is seen in |
| scan results. The method performs a max of |max_tries| connectivity scans |
| to find the network. |
| |
| Args: |
| ad: An AndroidDevice object. |
| network_ssid: SSID of the network we are looking for. |
| max_tries: Number of scans to try. |
| Returns: |
| True: if network_ssid is found in scan results. |
| False: if network_ssid is not found in scan results. |
| """ |
| start_time = time.time() |
| for num_tries in range(max_tries): |
| if start_wifi_connection_scan_and_return_status(ad): |
| scan_results = ad.droid.wifiGetScanResults() |
| match_results = match_networks( |
| {WifiEnums.SSID_KEY: network_ssid}, scan_results |
| ) |
| if len(match_results) > 0: |
| ad.log.debug(f"Found network in {time.time() - start_time} seconds.") |
| return True |
| ad.log.debug(f"Did not find network in {time.time() - start_time} seconds.") |
| return False |
| |
| |
| def start_wifi_connection_scan_and_ensure_network_found(ad, network_ssid, max_tries=3): |
| """ |
| Start connectivity scans & ensure the |network_ssid| is seen in |
| scan results. The method performs a max of |max_tries| connectivity scans |
| to find the network. |
| This method asserts on failure! |
| |
| Args: |
| ad: An AndroidDevice object. |
| network_ssid: SSID of the network we are looking for. |
| max_tries: Number of scans to try. |
| """ |
| ad.log.info("Starting scans to ensure %s is present", network_ssid) |
| assert_msg = ( |
| f"Failed to find {network_ssid} in scan results after {str(max_tries)} tries" |
| ) |
| asserts.assert_true( |
| start_wifi_connection_scan_and_check_for_network(ad, network_ssid, max_tries), |
| assert_msg, |
| ) |
| |
| |
| def start_wifi_connection_scan_and_ensure_network_not_found( |
| ad, network_ssid, max_tries=3 |
| ): |
| """ |
| Start connectivity scans & ensure the |network_ssid| is not seen in |
| scan results. The method performs a max of |max_tries| connectivity scans |
| to find the network. |
| This method asserts on failure! |
| |
| Args: |
| ad: An AndroidDevice object. |
| network_ssid: SSID of the network we are looking for. |
| max_tries: Number of scans to try. |
| """ |
| ad.log.info("Starting scans to ensure %s is not present", network_ssid) |
| assert_msg = f"Found {network_ssid} in scan results after {str(max_tries)} tries" |
| asserts.assert_false( |
| start_wifi_connection_scan_and_check_for_network(ad, network_ssid, max_tries), |
| assert_msg, |
| ) |
| |
| |
| def _wait_for_connect_event(ad, ssid=None, id=None, tries=1): |
| """Wait for a connect event on queue and pop when available. |
| |
| Args: |
| ad: An Android device object. |
| ssid: SSID of the network to connect to. |
| id: Network Id of the network to connect to. |
| tries: An integer that is the number of times to try before failing. |
| |
| Returns: |
| A dict with details of the connection data, which looks like this: |
| { |
| 'time': 1485460337798, |
| 'name': 'WifiNetworkConnected', |
| 'data': { |
| 'rssi': -27, |
| 'is_24ghz': True, |
| 'mac_address': '02:00:00:00:00:00', |
| 'network_id': 1, |
| 'BSSID': '30:b5:c2:33:d3:fc', |
| 'ip_address': 117483712, |
| 'link_speed': 54, |
| 'supplicant_state': 'completed', |
| 'hidden_ssid': False, |
| 'SSID': 'wh_ap1_2g', |
| 'is_5ghz': False} |
| } |
| |
| """ |
| conn_result = None |
| |
| # If ssid and network id is None, just wait for any connect event. |
| if id is None and ssid is None: |
| for i in range(tries): |
| try: |
| conn_result = ad.ed.pop_event(wifi_constants.WIFI_CONNECTED, 30) |
| break |
| except Empty: |
| pass |
| else: |
| # If ssid or network id is specified, wait for specific connect event. |
| for i in range(tries): |
| try: |
| conn_result = ad.ed.pop_event(wifi_constants.WIFI_CONNECTED, 30) |
| if id and conn_result["data"][WifiEnums.NETID_KEY] == id: |
| break |
| elif ssid and conn_result["data"][WifiEnums.SSID_KEY] == ssid: |
| break |
| except Empty: |
| pass |
| |
| return conn_result |
| |
| |
| def connect_to_wifi_network( |
| ad, |
| network, |
| assert_on_fail=True, |
| check_connectivity=True, |
| hidden=False, |
| num_of_scan_tries=DEFAULT_SCAN_TRIES, |
| num_of_connect_tries=DEFAULT_CONNECT_TRIES, |
| ): |
| """Connection logic for open and psk wifi networks. |
| |
| Args: |
| ad: AndroidDevice to use for connection |
| network: network info of the network to connect to |
| assert_on_fail: If true, errors from wifi_connect will raise |
| test failure signals. |
| hidden: Is the Wifi network hidden. |
| num_of_scan_tries: The number of times to try scan |
| interface before declaring failure. |
| num_of_connect_tries: The number of times to try |
| connect wifi before declaring failure. |
| """ |
| if hidden: |
| start_wifi_connection_scan_and_ensure_network_not_found( |
| ad, network[WifiEnums.SSID_KEY], max_tries=num_of_scan_tries |
| ) |
| else: |
| start_wifi_connection_scan_and_ensure_network_found( |
| ad, network[WifiEnums.SSID_KEY], max_tries=num_of_scan_tries |
| ) |
| wifi_connect( |
| ad, |
| network, |
| num_of_tries=num_of_connect_tries, |
| assert_on_fail=assert_on_fail, |
| check_connectivity=check_connectivity, |
| ) |
| |
| |
| def wifi_connect( |
| ad, network, num_of_tries=1, assert_on_fail=True, check_connectivity=True |
| ): |
| """Connect an Android device to a wifi network. |
| |
| Initiate connection to a wifi network, wait for the "connected" event, then |
| confirm the connected ssid is the one requested. |
| |
| This will directly fail a test if anything goes wrong. |
| |
| Args: |
| ad: android_device object to initiate connection on. |
| network: A dictionary representing the network to connect to. The |
| dictionary must have the key "SSID". |
| num_of_tries: An integer that is the number of times to try before |
| delaring failure. Default is 1. |
| assert_on_fail: If True, error checks in this function will raise test |
| failure signals. |
| |
| Returns: |
| Returns a value only if assert_on_fail is false. |
| Returns True if the connection was successful, False otherwise. |
| """ |
| return _assert_on_fail_handler( |
| _wifi_connect, |
| assert_on_fail, |
| ad, |
| network, |
| num_of_tries=num_of_tries, |
| check_connectivity=check_connectivity, |
| ) |
| |
| |
| def _wifi_connect(ad, network, num_of_tries=1, check_connectivity=True): |
| """Connect an Android device to a wifi network. |
| |
| Initiate connection to a wifi network, wait for the "connected" event, then |
| confirm the connected ssid is the one requested. |
| |
| This will directly fail a test if anything goes wrong. |
| |
| Args: |
| ad: android_device object to initiate connection on. |
| network: A dictionary representing the network to connect to. The |
| dictionary must have the key "SSID". |
| num_of_tries: An integer that is the number of times to try before |
| delaring failure. Default is 1. |
| """ |
| asserts.assert_true( |
| WifiEnums.SSID_KEY in network, |
| f"Key '{WifiEnums.SSID_KEY}' must be present in network definition.", |
| ) |
| ad.droid.wifiStartTrackingStateChange() |
| expected_ssid = network[WifiEnums.SSID_KEY] |
| ad.droid.wifiConnectByConfig(network) |
| ad.log.info("Starting connection process to %s", expected_ssid) |
| try: |
| ad.ed.pop_event(wifi_constants.CONNECT_BY_CONFIG_SUCCESS, 30) |
| connect_result = _wait_for_connect_event( |
| ad, ssid=expected_ssid, tries=num_of_tries |
| ) |
| asserts.assert_true( |
| connect_result, |
| f"Failed to connect to Wi-Fi network {network} on {ad.serial}", |
| ) |
| ad.log.debug("Wi-Fi connection result: %s.", connect_result) |
| actual_ssid = connect_result["data"][WifiEnums.SSID_KEY] |
| asserts.assert_equal( |
| actual_ssid, |
| expected_ssid, |
| f"Connected to the wrong network on {ad.serial}.", |
| ) |
| ad.log.info("Connected to Wi-Fi network %s.", actual_ssid) |
| |
| if check_connectivity: |
| internet = validate_connection(ad, DEFAULT_PING_ADDR) |
| if not internet: |
| raise signals.TestFailure( |
| f"Failed to connect to internet on {expected_ssid}" |
| ) |
| except Empty: |
| asserts.fail(f"Failed to start connection process to {network} on {ad.serial}") |
| except Exception as error: |
| ad.log.error("Failed to connect to %s with error %s", expected_ssid, error) |
| raise signals.TestFailure(f"Failed to connect to {network} network") |
| |
| finally: |
| ad.droid.wifiStopTrackingStateChange() |
| |
| |
| def validate_connection( |
| ad, ping_addr=DEFAULT_PING_ADDR, wait_time=15, ping_gateway=True |
| ): |
| """Validate internet connection by pinging the address provided. |
| |
| Args: |
| ad: android_device object. |
| ping_addr: address on internet for pinging. |
| wait_time: wait for some time before validating connection |
| |
| Returns: |
| ping output if successful, NULL otherwise. |
| """ |
| android_version = int(ad.adb.shell("getprop ro.vendor.build.version.release")) |
| # wait_time to allow for DHCP to complete. |
| for i in range(wait_time): |
| if ad.droid.connectivityNetworkIsConnected(): |
| if ( |
| android_version > 10 and ad.droid.connectivityGetIPv4DefaultGateway() |
| ) or android_version < 11: |
| break |
| time.sleep(1) |
| ping = False |
| try: |
| ping = ad.droid.httpPing(ping_addr) |
| ad.log.info("Http ping result: %s.", ping) |
| except: |
| pass |
| if android_version > 10 and not ping and ping_gateway: |
| ad.log.info("Http ping failed. Pinging default gateway") |
| gw = ad.droid.connectivityGetIPv4DefaultGateway() |
| result = ad.adb.shell(f"ping -c 6 {gw}") |
| ad.log.info(f"Default gateway ping result: {result}") |
| ping = False if "100% packet loss" in result else True |
| return ping |
| |
| |
| # TODO(angli): This can only verify if an actual value is exactly the same. |
| # Would be nice to be able to verify an actual value is one of serveral. |
| def verify_wifi_connection_info(ad, expected_con): |
| """Verifies that the information of the currently connected wifi network is |
| as expected. |
| |
| Args: |
| expected_con: A dict representing expected key-value pairs for wifi |
| connection. e.g. {"SSID": "test_wifi"} |
| """ |
| current_con = ad.droid.wifiGetConnectionInfo() |
| case_insensitive = ["BSSID", "supplicant_state"] |
| ad.log.debug("Current connection: %s", current_con) |
| for k, expected_v in expected_con.items(): |
| # Do not verify authentication related fields. |
| if k == "password": |
| continue |
| msg = f"Field {k} does not exist in wifi connection info {current_con}." |
| if k not in current_con: |
| raise signals.TestFailure(msg) |
| actual_v = current_con[k] |
| if k in case_insensitive: |
| actual_v = actual_v.lower() |
| expected_v = expected_v.lower() |
| msg = f"Expected {k} to be {expected_v}, actual {k} is {actual_v}." |
| if actual_v != expected_v: |
| raise signals.TestFailure(msg) |
| |
| |
| def get_current_softap_capability(ad, callbackId, need_to_wait): |
| """pop up all of softap info list changed event from queue. |
| Args: |
| callbackId: Id of the callback associated with registering. |
| need_to_wait: Wait for the info callback event before pop all. |
| Returns: |
| Returns last updated capability of softap. |
| """ |
| eventStr = ( |
| wifi_constants.SOFTAP_CALLBACK_EVENT |
| + str(callbackId) |
| + wifi_constants.SOFTAP_CAPABILITY_CHANGED |
| ) |
| ad.log.debug("softap capability dump from eventStr %s", eventStr) |
| if need_to_wait: |
| event = ad.ed.pop_event(eventStr, SHORT_TIMEOUT) |
| capability = event["data"] |
| |
| events = ad.ed.pop_all(eventStr) |
| for event in events: |
| capability = event["data"] |
| |
| return capability |
| |
| |
| def get_ssrdumps(ad): |
| """Pulls dumps in the ssrdump dir |
| Args: |
| ad: android device object. |
| """ |
| logs = ad.get_file_names("/data/vendor/ssrdump/") |
| if logs: |
| ad.log.info("Pulling ssrdumps %s", logs) |
| log_path = os.path.join(ad.device_log_path, f"SSRDUMPS_{ad.serial}") |
| os.makedirs(log_path, exist_ok=True) |
| ad.pull_files(logs, log_path) |
| ad.adb.shell("find /data/vendor/ssrdump/ -type f -delete", ignore_status=True) |
| |
| |
| def start_pcap(pcap, wifi_band, test_name): |
| """Start packet capture in monitor mode. |
| |
| Args: |
| pcap: packet capture object |
| wifi_band: '2g' or '5g' or 'dual' |
| test_name: test name to be used for pcap file name |
| |
| Returns: |
| Dictionary with wifi band as key and the tuple |
| (pcap Process object, log directory) as the value |
| """ |
| log_dir = os.path.join( |
| context.get_current_context().get_full_output_path(), "PacketCapture" |
| ) |
| os.makedirs(log_dir, exist_ok=True) |
| if wifi_band == "dual": |
| bands = [BAND_2G, BAND_5G] |
| else: |
| bands = [wifi_band] |
| procs = {} |
| for band in bands: |
| proc = pcap.start_packet_capture(band, log_dir, test_name) |
| procs[band] = (proc, os.path.join(log_dir, test_name)) |
| return procs |
| |
| |
| def stop_pcap(pcap, procs, test_status=None): |
| """Stop packet capture in monitor mode. |
| |
| Since, the pcap logs in monitor mode can be very large, we will |
| delete them if they are not required. 'test_status' if True, will delete |
| the pcap files. If False, we will keep them. |
| |
| Args: |
| pcap: packet capture object |
| procs: dictionary returned by start_pcap |
| test_status: status of the test case |
| """ |
| for proc, fname in procs.values(): |
| pcap.stop_packet_capture(proc) |
| |
| if test_status: |
| shutil.rmtree(os.path.dirname(fname)) |
| |
| |
| def start_cnss_diags(ads, cnss_diag_file, pixel_models): |
| for ad in ads: |
| start_cnss_diag(ad, cnss_diag_file, pixel_models) |
| |
| |
| def start_cnss_diag(ad, cnss_diag_file, pixel_models): |
| """Start cnss_diag to record extra wifi logs |
| |
| Args: |
| ad: android device object. |
| cnss_diag_file: cnss diag config file to push to device. |
| pixel_models: pixel devices. |
| """ |
| if ad.model not in pixel_models: |
| ad.log.info("Device not supported to collect pixel logger") |
| return |
| if ad.model in wifi_constants.DEVICES_USING_LEGACY_PROP: |
| prop = wifi_constants.LEGACY_CNSS_DIAG_PROP |
| else: |
| prop = wifi_constants.CNSS_DIAG_PROP |
| if ad.adb.getprop(prop) != "true": |
| if not int( |
| ad.adb.shell( |
| f"ls -l {CNSS_DIAG_CONFIG_PATH}{CNSS_DIAG_CONFIG_FILE} | wc -l" |
| ) |
| ): |
| ad.adb.push(f"{cnss_diag_file} {CNSS_DIAG_CONFIG_PATH}") |
| ad.adb.shell( |
| "find /data/vendor/wifi/cnss_diag/wlan_logs/ -type f -delete", |
| ignore_status=True, |
| ) |
| ad.adb.shell(f"setprop {prop} true", ignore_status=True) |
| |
| |
| def stop_cnss_diags(ads, pixel_models): |
| for ad in ads: |
| stop_cnss_diag(ad, pixel_models) |
| |
| |
| def stop_cnss_diag(ad, pixel_models): |
| """Stops cnss_diag |
| |
| Args: |
| ad: android device object. |
| pixel_models: pixel devices. |
| """ |
| if ad.model not in pixel_models: |
| ad.log.info("Device not supported to collect pixel logger") |
| return |
| if ad.model in wifi_constants.DEVICES_USING_LEGACY_PROP: |
| prop = wifi_constants.LEGACY_CNSS_DIAG_PROP |
| else: |
| prop = wifi_constants.CNSS_DIAG_PROP |
| ad.adb.shell(f"setprop {prop} false", ignore_status=True) |
| |
| |
| def get_cnss_diag_log(ad): |
| """Pulls the cnss_diag logs in the wlan_logs dir |
| Args: |
| ad: android device object. |
| """ |
| logs = ad.get_file_names("/data/vendor/wifi/cnss_diag/wlan_logs/") |
| if logs: |
| ad.log.info("Pulling cnss_diag logs %s", logs) |
| log_path = os.path.join(ad.device_log_path, f"CNSS_DIAG_{ad.serial}") |
| os.makedirs(log_path, exist_ok=True) |
| ad.pull_files(logs, log_path) |
| |
| |
| def turn_location_off_and_scan_toggle_off(ad): |
| """Turns off wifi location scans.""" |
| utils.set_location_service(ad, False) |
| ad.droid.wifiScannerToggleAlwaysAvailable(False) |
| msg = "Failed to turn off location service's scan." |
| asserts.assert_true(not ad.droid.wifiScannerIsAlwaysAvailable(), msg) |