Home | History | Annotate | Download | only in network
      1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import logging
      6 import math
      7 import os
      8 import re
      9 import time
     10 
     11 from contextlib import contextmanager
     12 from collections import namedtuple
     13 
     14 from autotest_lib.client.bin import site_utils as client_site_utils
     15 from autotest_lib.client.bin import utils
     16 from autotest_lib.client.common_lib import base_utils
     17 from autotest_lib.client.common_lib import error
     18 from autotest_lib.client.common_lib.cros.network import interface
     19 from autotest_lib.client.common_lib.cros.network import iw_runner
     20 from autotest_lib.client.common_lib.cros.network import ping_runner
     21 from autotest_lib.client.cros import constants
     22 from autotest_lib.server import autotest
     23 from autotest_lib.server import frontend
     24 from autotest_lib.server import site_linux_system
     25 from autotest_lib.server import site_utils
     26 from autotest_lib.server.cros.network import wpa_cli_proxy
     27 from autotest_lib.server.hosts import adb_host
     28 
     29 # Wake-on-WiFi feature strings
     30 WAKE_ON_WIFI_NONE = 'none'
     31 WAKE_ON_WIFI_PACKET = 'packet'
     32 WAKE_ON_WIFI_DARKCONNECT = 'darkconnect'
     33 WAKE_ON_WIFI_PACKET_DARKCONNECT = 'packet_and_darkconnect'
     34 WAKE_ON_WIFI_NOT_SUPPORTED = 'not_supported'
     35 
     36 # Wake-on-WiFi test timing constants
     37 SUSPEND_WAIT_TIME_SECONDS = 10
     38 RECEIVE_PACKET_WAIT_TIME_SECONDS = 10
     39 DARK_RESUME_WAIT_TIME_SECONDS = 25
     40 WAKE_TO_SCAN_PERIOD_SECONDS = 30
     41 NET_DETECT_SCAN_WAIT_TIME_SECONDS = 15
     42 WAIT_UP_TIMEOUT_SECONDS = 10
     43 DISCONNECT_WAIT_TIME_SECONDS = 10
     44 INTERFACE_DOWN_WAIT_TIME_SECONDS = 10
     45 
     46 ConnectTime = namedtuple('ConnectTime', 'state, time')
     47 
     48 XMLRPC_BRINGUP_TIMEOUT_SECONDS = 60
     49 SHILL_XMLRPC_LOG_PATH = '/var/log/shill_xmlrpc_server.log'
     50 SHILL_BRILLO_XMLRPC_LOG_PATH = '/data/shill_xmlrpc_server.log'
     51 ANDROID_XMLRPC_LOG_PATH = '/var/log/android_xmlrpc_server.log'
     52 ANDROID_XMLRPC_SERVER_AUTOTEST_PATH = (
     53         '../../../client/cros/networking/android_xmlrpc_server.py')
     54 
     55 def install_android_xmlrpc_server(host):
     56     """Install Android XMLRPC server script on |host|.
     57 
     58     @param host: host object representing a remote device.
     59 
     60     """
     61     current_dir = os.path.dirname(os.path.realpath(__file__))
     62     xmlrpc_server_script = os.path.join(
     63             current_dir, ANDROID_XMLRPC_SERVER_AUTOTEST_PATH)
     64     host.send_file(
     65             xmlrpc_server_script, constants.ANDROID_XMLRPC_SERVER_TARGET_DIR)
     66 
     67 
     68 def get_xmlrpc_proxy(host):
     69     """Get a shill XMLRPC proxy for |host|.
     70 
     71     The returned object has no particular type.  Instead, when you call
     72     a method on the object, it marshalls the objects passed as arguments
     73     and uses them to make RPCs on the remote server.  Thus, you should
     74     read shill_xmlrpc_server.py to find out what methods are supported.
     75 
     76     @param host: host object representing a remote device.
     77     @return proxy object for remote XMLRPC server.
     78 
     79     """
     80     # Make sure the client library is on the device so that the proxy
     81     # code is there when we try to call it.
     82     if host.is_client_install_supported:
     83         client_at = autotest.Autotest(host)
     84         client_at.install()
     85     if host.get_os_type() == adb_host.OS_TYPE_BRILLO:
     86         xmlrpc_server_command = constants.SHILL_BRILLO_XMLRPC_SERVER_COMMAND
     87         log_path = SHILL_BRILLO_XMLRPC_LOG_PATH
     88         command_name = constants.SHILL_BRILLO_XMLRPC_SERVER_CLEANUP_PATTERN
     89     elif host.get_os_type() == adb_host.OS_TYPE_ANDROID:
     90         xmlrpc_server_command = constants.ANDROID_XMLRPC_SERVER_COMMAND
     91         log_path = ANDROID_XMLRPC_LOG_PATH
     92         command_name = constants.ANDROID_XMLRPC_SERVER_CLEANUP_PATTERN
     93         # If there is more than one device attached to the host, use serial to
     94         # identify the DUT.
     95         if host.adb_serial:
     96             xmlrpc_server_command = (
     97                     '%s -s %s' % (xmlrpc_server_command, host.adb_serial))
     98         # For android, start the XML RPC server on the accompanying host.
     99         host = host.teststation
    100         install_android_xmlrpc_server(host)
    101     else:
    102         xmlrpc_server_command = constants.SHILL_XMLRPC_SERVER_COMMAND
    103         log_path = SHILL_XMLRPC_LOG_PATH
    104         command_name = constants.SHILL_XMLRPC_SERVER_CLEANUP_PATTERN
    105     # Start up the XMLRPC proxy on the client
    106     proxy = host.rpc_server_tracker.xmlrpc_connect(
    107             xmlrpc_server_command,
    108             constants.SHILL_XMLRPC_SERVER_PORT,
    109             command_name=command_name,
    110             ready_test_name=constants.SHILL_XMLRPC_SERVER_READY_METHOD,
    111             timeout_seconds=XMLRPC_BRINGUP_TIMEOUT_SECONDS,
    112             logfile=log_path
    113       )
    114     return proxy
    115 
    116 
    117 def _is_conductive(hostname):
    118     if utils.host_could_be_in_afe(hostname):
    119         conductive = site_utils.get_label_from_afe(hostname.split('.')[0],
    120                                                   'conductive:',
    121                                                    frontend.AFE())
    122         if conductive and conductive.lower() == 'true':
    123             return True
    124     return False
    125 
    126 
    127 class WiFiClient(site_linux_system.LinuxSystem):
    128     """WiFiClient is a thin layer of logic over a remote DUT in wifitests."""
    129 
    130     DEFAULT_PING_COUNT = 10
    131     COMMAND_PING = 'ping'
    132 
    133     MAX_SERVICE_GONE_TIMEOUT_SECONDS = 60
    134 
    135     # List of interface names we won't consider for use as "the" WiFi interface
    136     # on Android hosts.
    137     WIFI_IF_BLACKLIST = ['p2p0']
    138 
    139     UNKNOWN_BOARD_TYPE = 'unknown'
    140 
    141     # DBus device properties. Wireless interfaces should support these.
    142     ROAM_THRESHOLD = 'RoamThreshold'
    143     WAKE_ON_WIFI_FEATURES = 'WakeOnWiFiFeaturesEnabled'
    144     NET_DETECT_SCAN_PERIOD = 'NetDetectScanPeriodSeconds'
    145     WAKE_TO_SCAN_PERIOD = 'WakeToScanPeriodSeconds'
    146     FORCE_WAKE_TO_SCAN_TIMER = 'ForceWakeToScanTimer'
    147 
    148     CONNECTED_STATES = ['ready', 'portal', 'online']
    149 
    150 
    151     @property
    152     def machine_id(self):
    153         """@return string unique to a particular board/cpu configuration."""
    154         if self._machine_id:
    155             return self._machine_id
    156 
    157         uname_result = self.host.run('uname -m', ignore_status=True)
    158         kernel_arch = ''
    159         if not uname_result.exit_status and uname_result.stdout.find(' ') < 0:
    160             kernel_arch = uname_result.stdout.strip()
    161         cpu_info = self.host.run('cat /proc/cpuinfo').stdout.splitlines()
    162         cpu_count = len(filter(lambda x: x.lower().startswith('bogomips'),
    163                                cpu_info))
    164         cpu_count_str = ''
    165         if cpu_count:
    166             cpu_count_str = 'x%d' % cpu_count
    167         ghz_value = ''
    168         ghz_pattern = re.compile('([0-9.]+GHz)')
    169         for line in cpu_info:
    170             match = ghz_pattern.search(line)
    171             if match is not None:
    172                 ghz_value = '_' + match.group(1)
    173                 break
    174 
    175         return '%s_%s%s%s' % (self.board, kernel_arch, ghz_value, cpu_count_str)
    176 
    177 
    178     @property
    179     def powersave_on(self):
    180         """@return bool True iff WiFi powersave mode is enabled."""
    181         result = self.host.run("iw dev %s get power_save" % self.wifi_if)
    182         output = result.stdout.rstrip()       # NB: chop \n
    183         # Output should be either "Power save: on" or "Power save: off".
    184         find_re = re.compile('([^:]+):\s+(\w+)')
    185         find_results = find_re.match(output)
    186         if not find_results:
    187             raise error.TestFail('Failed to find power_save parameter '
    188                                  'in iw results.')
    189 
    190         return find_results.group(2) == 'on'
    191 
    192 
    193     @property
    194     def shill(self):
    195         """@return shill RPCProxy object."""
    196         return self._shill_proxy
    197 
    198 
    199     @property
    200     def client(self):
    201         """Deprecated accessor for the client host.
    202 
    203         The term client is used very loosely in old autotests and this
    204         accessor should not be used in new code.  Use host() instead.
    205 
    206         @return host object representing a remote DUT.
    207 
    208         """
    209         return self.host
    210 
    211 
    212     @property
    213     def command_ip(self):
    214         """@return string path to ip command."""
    215         return self._command_ip
    216 
    217 
    218     @property
    219     def command_iptables(self):
    220         """@return string path to iptables command."""
    221         return self._command_iptables
    222 
    223 
    224     @property
    225     def command_ping6(self):
    226         """@return string path to ping6 command."""
    227         return self._command_ping6
    228 
    229 
    230     @property
    231     def command_wpa_cli(self):
    232         """@return string path to wpa_cli command."""
    233         return self._command_wpa_cli
    234 
    235 
    236     @property
    237     def conductive(self):
    238         """@return True if the rig is conductive; False otherwise."""
    239         if self._conductive is None:
    240             self._conductive = _is_conductive(self._client_hostname)
    241         return self._conductive
    242 
    243 
    244     @conductive.setter
    245     def conductive(self, value):
    246         """Set the conductive member to True or False.
    247 
    248         @param value: boolean value to set the conductive member to.
    249         """
    250         self._conductive = value
    251 
    252 
    253     @property
    254     def wifi_if(self):
    255         """@return string wifi device on machine (e.g. mlan0)."""
    256         return self._wifi_if
    257 
    258 
    259     @property
    260     def wifi_mac(self):
    261         """@return string MAC address of self.wifi_if."""
    262         return self._interface.mac_address
    263 
    264 
    265     @property
    266     def wifi_ip(self):
    267         """@return string IPv4 address of self.wifi_if."""
    268         return self._interface.ipv4_address
    269 
    270 
    271     @property
    272     def wifi_ip_subnet(self):
    273         """@return string IPv4 subnet prefix of self.wifi_if."""
    274         return self._interface.ipv4_subnet
    275 
    276 
    277     @property
    278     def wifi_signal_level(self):
    279         """Returns the signal level of this DUT's WiFi interface.
    280 
    281         @return int signal level of connected WiFi interface or None (e.g. -67).
    282 
    283         """
    284         return self._interface.signal_level
    285 
    286 
    287     @staticmethod
    288     def assert_bsses_include_ssids(found_bsses, expected_ssids):
    289         """Verifies that |found_bsses| includes |expected_ssids|.
    290 
    291         @param found_bsses list of IwBss objects.
    292         @param expected_ssids list of string SSIDs.
    293         @raise error.TestFail if any element of |expected_ssids| is not found.
    294 
    295         """
    296         for ssid in expected_ssids:
    297             if not ssid:
    298                 continue
    299 
    300             for bss in found_bsses:
    301                 if bss.ssid == ssid:
    302                     break
    303             else:
    304                 raise error.TestFail('SSID %s is not in scan results: %r' %
    305                                      (ssid, found_bsses))
    306 
    307 
    308     def wifi_noise_level(self, frequency_mhz):
    309         """Returns the noise level of this DUT's WiFi interface.
    310 
    311         @param frequency_mhz: frequency at which the noise level should be
    312                measured and reported.
    313         @return int signal level of connected WiFi interface in dBm (e.g. -67)
    314                 or None if the value is unavailable.
    315 
    316         """
    317         return self._interface.noise_level(frequency_mhz)
    318 
    319 
    320     def __init__(self, client_host, result_dir, use_wpa_cli):
    321         """
    322         Construct a WiFiClient.
    323 
    324         @param client_host host object representing a remote host.
    325         @param result_dir string directory to store test logs/packet caps.
    326         @param use_wpa_cli bool True if we want to use |wpa_cli| commands for
    327                Android testing.
    328 
    329         """
    330         super(WiFiClient, self).__init__(client_host, 'client',
    331                                          inherit_interfaces=True)
    332         self._command_ip = 'ip'
    333         self._command_iptables = 'iptables'
    334         self._command_ping6 = 'ping6'
    335         self._command_wpa_cli = 'wpa_cli'
    336         self._machine_id = None
    337         self._result_dir = result_dir
    338         self._conductive = None
    339         self._client_hostname = client_host.hostname
    340 
    341         if self.host.get_os_type() == adb_host.OS_TYPE_ANDROID and use_wpa_cli:
    342             # Look up the WiFi device (and its MAC) on the client.
    343             devs = self.iw_runner.list_interfaces(desired_if_type='managed')
    344             devs = [dev for dev in devs
    345                     if dev.if_name not in self.WIFI_IF_BLACKLIST]
    346             if not devs:
    347                 raise error.TestFail('No wlan devices found on %s.' %
    348                                      self.host.hostname)
    349 
    350             if len(devs) > 1:
    351                 logging.warning('Warning, found multiple WiFi devices on '
    352                                 '%s: %r', self.host.hostname, devs)
    353             self._wifi_if = devs[0].if_name
    354             self._shill_proxy = wpa_cli_proxy.WpaCliProxy(
    355                     self.host, self._wifi_if)
    356             self._wpa_cli_proxy = self._shill_proxy
    357         else:
    358             self._shill_proxy = get_xmlrpc_proxy(self.host)
    359             interfaces = self._shill_proxy.list_controlled_wifi_interfaces()
    360             if not interfaces:
    361                 logging.debug('No interfaces managed by shill. Rebooting host')
    362                 self.host.reboot()
    363                 raise error.TestError('No interfaces managed by shill on %s' %
    364                                       self.host.hostname)
    365             self._wifi_if = interfaces[0]
    366             self._wpa_cli_proxy = wpa_cli_proxy.WpaCliProxy(
    367                     self.host, self._wifi_if)
    368             self._raise_logging_level()
    369         self._interface = interface.Interface(self._wifi_if, host=self.host)
    370         logging.debug('WiFi interface is: %r',
    371                       self._interface.device_description)
    372         self._firewall_rules = []
    373         # Turn off powersave mode by default.
    374         self.powersave_switch(False)
    375         # All tests that use this object assume the interface starts enabled.
    376         self.set_device_enabled(self._wifi_if, True)
    377         # Invoke the |capabilities| property defined in the parent |Linuxsystem|
    378         # to workaround the lazy loading of the capabilities cache and supported
    379         # frequency list. This is needed for tests that may need access to these
    380         # when the DUT is unreachable (for ex: suspended).
    381         self.capabilities
    382 
    383 
    384     def _assert_method_supported(self, method_name):
    385         """Raise a TestNAError if the XMLRPC proxy has no method |method_name|.
    386 
    387         @param method_name: string name of method that should exist on the
    388                 XMLRPC proxy.
    389 
    390         """
    391         if not self._supports_method(method_name):
    392             raise error.TestNAError('%s() is not supported' % method_name)
    393 
    394 
    395     def _raise_logging_level(self):
    396         """Raises logging levels for WiFi on DUT."""
    397         self.host.run('wpa_debug excessive', ignore_status=True)
    398         self.host.run('ff_debug --level -5', ignore_status=True)
    399         self.host.run('ff_debug +wifi', ignore_status=True)
    400 
    401 
    402     def is_vht_supported(self):
    403         """Returns True if VHT supported; False otherwise"""
    404         return self.CAPABILITY_VHT in self.capabilities
    405 
    406 
    407     def is_5ghz_supported(self):
    408         """Returns True if 5Ghz bands are supported; False otherwise."""
    409         return self.CAPABILITY_5GHZ in self.capabilities
    410 
    411 
    412     def is_ibss_supported(self):
    413         """Returns True if IBSS mode is supported; False otherwise."""
    414         return self.CAPABILITY_IBSS in self.capabilities
    415 
    416 
    417     def is_frequency_supported(self, frequency):
    418         """Returns True if the given frequency is supported; False otherwise.
    419 
    420         @param frequency: int Wifi frequency to check if it is supported by
    421                           DUT.
    422         """
    423         return frequency in self.phys_for_frequency
    424 
    425 
    426     def _supports_method(self, method_name):
    427         """Checks if |method_name| is supported on the remote XMLRPC proxy.
    428 
    429         autotest will, for their own reasons, install python files in the
    430         autotest client package that correspond the version of the build
    431         rather than the version running on the autotest drone.  This
    432         creates situations where we call methods on the client XMLRPC proxy
    433         that don't exist in that version of the code.  This detects those
    434         situations so that we can degrade more or less gracefully.
    435 
    436         @param method_name: string name of method that should exist on the
    437                 XMLRPC proxy.
    438         @return True if method is available, False otherwise.
    439 
    440         """
    441         # Make no assertions about ADBHost support.  We don't use an XMLRPC
    442         # proxy with those hosts anyway.
    443         supported = (isinstance(self.host, adb_host.ADBHost) or
    444                      method_name in self._shill_proxy.system.listMethods())
    445         if not supported:
    446             logging.warning('%s() is not supported on older images',
    447                             method_name)
    448         return supported
    449 
    450 
    451     def close(self):
    452         """Tear down state associated with the client."""
    453         self.stop_capture()
    454         self.powersave_switch(False)
    455         self.shill.clean_profiles()
    456         super(WiFiClient, self).close()
    457 
    458 
    459     def firewall_open(self, proto, src):
    460         """Opens up firewall to run netperf tests.
    461 
    462         By default, we have a firewall rule for NFQUEUE (see crbug.com/220736).
    463         In order to run netperf test, we need to add a new firewall rule BEFORE
    464         this NFQUEUE rule in the INPUT chain.
    465 
    466         @param proto a string, test traffic protocol, e.g. udp, tcp.
    467         @param src a string, subnet/mask.
    468 
    469         @return a string firewall rule added.
    470 
    471         """
    472         rule = 'INPUT -s %s/32 -p %s -m %s -j ACCEPT' % (src, proto, proto)
    473         self.host.run('%s -I %s' % (self._command_iptables, rule))
    474         self._firewall_rules.append(rule)
    475         return rule
    476 
    477 
    478     def firewall_cleanup(self):
    479         """Cleans up firewall rules."""
    480         for rule in self._firewall_rules:
    481             self.host.run('%s -D %s' % (self._command_iptables, rule))
    482         self._firewall_rules = []
    483 
    484 
    485     def sync_host_times(self):
    486         """Set time on our DUT to match local time."""
    487         epoch_seconds = time.time()
    488         self.shill.sync_time_to(epoch_seconds)
    489 
    490 
    491     def check_iw_link_value(self, iw_link_key, desired_value):
    492         """Assert that the current wireless link property is |desired_value|.
    493 
    494         @param iw_link_key string one of IW_LINK_KEY_* defined in iw_runner.
    495         @param desired_value string desired value of iw link property.
    496 
    497         """
    498         actual_value = self.get_iw_link_value(iw_link_key)
    499         desired_value = str(desired_value)
    500         if actual_value != desired_value:
    501             raise error.TestFail('Wanted iw link property %s value %s, but '
    502                                  'got %s instead.' % (iw_link_key,
    503                                                       desired_value,
    504                                                       actual_value))
    505 
    506 
    507     def get_iw_link_value(self, iw_link_key):
    508         """Get the current value of a link property for this WiFi interface.
    509 
    510         @param iw_link_key string one of IW_LINK_KEY_* defined in iw_runner.
    511 
    512         """
    513         return self.iw_runner.get_link_value(self.wifi_if, iw_link_key)
    514 
    515 
    516     def powersave_switch(self, turn_on):
    517         """Toggle powersave mode for the DUT.
    518 
    519         @param turn_on bool True iff powersave mode should be turned on.
    520 
    521         """
    522         mode = 'off'
    523         if turn_on:
    524             mode = 'on'
    525         self.host.run('iw dev %s set power_save %s' % (self.wifi_if, mode))
    526 
    527 
    528     def timed_scan(self, frequencies, ssids, scan_timeout_seconds=10,
    529                    retry_timeout_seconds=10):
    530         """Request timed scan to discover given SSIDs.
    531 
    532         This method will retry for a default of |retry_timeout_seconds| until it
    533         is able to successfully kick off a scan.  Sometimes, the device on the
    534         DUT claims to be busy and rejects our requests. It will raise error
    535         if the scan did not complete within |scan_timeout_seconds| or it was
    536         not able to discover the given SSIDs.
    537 
    538         @param frequencies list of int WiFi frequencies to scan for.
    539         @param ssids list of string ssids to probe request for.
    540         @param scan_timeout_seconds: float number of seconds the scan
    541                 operation not to exceed.
    542         @param retry_timeout_seconds: float number of seconds to retry scanning
    543                 if the interface is busy.  This does not retry if certain
    544                 SSIDs are missing from the results.
    545         @return time in seconds took to complete scan request.
    546 
    547         """
    548         start_time = time.time()
    549         while time.time() - start_time < retry_timeout_seconds:
    550             scan_result = self.iw_runner.timed_scan(
    551                     self.wifi_if, frequencies=frequencies, ssids=ssids)
    552 
    553             if scan_result is not None:
    554                 break
    555 
    556             time.sleep(0.5)
    557         else:
    558             raise error.TestFail('Unable to trigger scan on client.')
    559 
    560         # Verify scan operation completed within given timeout
    561         if scan_result.time > scan_timeout_seconds:
    562             raise error.TestFail('Scan time %.2fs exceeds the scan timeout' %
    563                                  (scan_result.time))
    564 
    565         # Verify all ssids are discovered
    566         self.assert_bsses_include_ssids(scan_result.bss_list, ssids)
    567 
    568         logging.info('Wifi scan completed in %.2f seconds', scan_result.time)
    569         return scan_result.time
    570 
    571 
    572     def scan(self, frequencies, ssids, timeout_seconds=10, require_match=True):
    573         """Request a scan and (optionally) check that requested SSIDs appear in
    574         the results.
    575 
    576         This method will retry for a default of |timeout_seconds| until it is
    577         able to successfully kick off a scan.  Sometimes, the device on the DUT
    578         claims to be busy and rejects our requests.
    579 
    580         If |ssids| is non-empty, we will speficially probe for those SSIDs.
    581 
    582         If |require_match| is True, we will verify that every element
    583         of |ssids| was found in scan results.
    584 
    585         @param frequencies list of int WiFi frequencies to scan for.
    586         @param ssids list of string ssids to probe request for.
    587         @param timeout_seconds: float number of seconds to retry scanning
    588                 if the interface is busy.  This does not retry if certain
    589                 SSIDs are missing from the results.
    590         @param require_match: bool True if we must find |ssids|.
    591 
    592         """
    593         start_time = time.time()
    594         while time.time() - start_time < timeout_seconds:
    595             bss_list = self.iw_runner.scan(
    596                     self.wifi_if, frequencies=frequencies, ssids=ssids)
    597             if bss_list is not None:
    598                 break
    599 
    600             time.sleep(0.5)
    601         else:
    602             raise error.TestFail('Unable to trigger scan on client.')
    603 
    604         if require_match:
    605             self.assert_bsses_include_ssids(bss_list, ssids)
    606 
    607 
    608     def wait_for_bsses(self, ssid, num_bss_expected, timeout_seconds=15):
    609       """Wait for all BSSes associated with given SSID to be discovered in the
    610       scan.
    611 
    612       @param ssid string name of network being queried
    613       @param num_bss_expected int number of BSSes expected
    614       @param timeout_seconds int seconds to wait for BSSes to be discovered
    615 
    616       """
    617       start_time = time.time()
    618       while time.time() - start_time < timeout_seconds:
    619           bss_list = self.iw_runner.scan(
    620                   self.wifi_if, frequencies=[], ssids=[ssid])
    621 
    622           # Determine number of BSSes found in the scan result.
    623           num_bss_found = 0
    624           if bss_list is not None:
    625               for bss in bss_list:
    626                   if bss.ssid == ssid:
    627                       num_bss_found += 1
    628 
    629           # Verify all BSSes are found.
    630           if num_bss_found == num_bss_expected:
    631               break
    632 
    633           time.sleep(0.5)
    634       else:
    635           raise error.TestFail('Failed to discover all BSSes.')
    636 
    637 
    638     def wait_for_service_states(self, ssid, states, timeout_seconds):
    639         """Waits for a WiFi service to achieve one of |states|.
    640 
    641         @param ssid string name of network being queried
    642         @param states tuple list of states for which the caller is waiting
    643         @param timeout_seconds int seconds to wait for a state in |states|
    644 
    645         """
    646         logging.info('Waiting for %s to reach one of %r...', ssid, states)
    647         success, state, time  = self._shill_proxy.wait_for_service_states(
    648                 ssid, states, timeout_seconds)
    649         logging.info('...ended up in state \'%s\' (%s) after %f seconds.',
    650                      state, 'success' if success else 'failure', time)
    651         return success, state, time
    652 
    653 
    654     def do_suspend(self, seconds):
    655         """Puts the DUT in suspend power state for |seconds| seconds.
    656 
    657         @param seconds: The number of seconds to suspend the device.
    658 
    659         """
    660         logging.info('Suspending DUT for %d seconds...', seconds)
    661         self._shill_proxy.do_suspend(seconds)
    662         logging.info('...done suspending')
    663 
    664 
    665     def do_suspend_bg(self, seconds):
    666         """Suspend DUT using the power manager - non-blocking.
    667 
    668         @param seconds: The number of seconds to suspend the device.
    669 
    670         """
    671         logging.info('Suspending DUT (in background) for %d seconds...',
    672                      seconds)
    673         self._shill_proxy.do_suspend_bg(seconds)
    674 
    675 
    676     def clear_supplicant_blacklist(self):
    677         """Clear's the AP blacklist on the DUT.
    678 
    679         @return stdout and stderror returns passed from wpa_cli command.
    680 
    681         """
    682         result = self._wpa_cli_proxy.run_wpa_cli_cmd('blacklist clear',
    683                                                      check_result=False);
    684         logging.info('wpa_cli blacklist clear: out:%r err:%r', result.stdout,
    685                      result.stderr)
    686         return result.stdout, result.stderr
    687 
    688 
    689     def get_active_wifi_SSIDs(self):
    690         """Get a list of visible SSID's around the DUT
    691 
    692         @return list of string SSIDs
    693 
    694         """
    695         self._assert_method_supported('get_active_wifi_SSIDs')
    696         return self._shill_proxy.get_active_wifi_SSIDs()
    697 
    698 
    699     def roam_threshold(self, value):
    700         """Get a context manager to temporarily change wpa_supplicant's
    701         roaming threshold for the specified interface.
    702 
    703         The correct way to use this method is:
    704 
    705         with client.roam_threshold(40):
    706             ...
    707 
    708         @param value: the desired roam threshold for the test.
    709 
    710         @return a context manager for the threshold.
    711 
    712         """
    713         return TemporaryDBusProperty(self._shill_proxy,
    714                                      self.wifi_if,
    715                                      self.ROAM_THRESHOLD,
    716                                      value)
    717 
    718 
    719     def set_device_enabled(self, wifi_interface, value,
    720                            fail_on_unsupported=False):
    721         """Enable or disable the WiFi device.
    722 
    723         @param wifi_interface: string name of interface being modified.
    724         @param enabled: boolean; true if this device should be enabled,
    725                 false if this device should be disabled.
    726         @return True if it worked; False, otherwise.
    727 
    728         """
    729         if fail_on_unsupported:
    730             self._assert_method_supported('set_device_enabled')
    731         elif not self._supports_method('set_device_enabled'):
    732             return False
    733         return self._shill_proxy.set_device_enabled(wifi_interface, value)
    734 
    735 
    736     def add_arp_entry(self, ip_address, mac_address):
    737         """Add an ARP entry to the table associated with the WiFi interface.
    738 
    739         @param ip_address: string IP address associated with the new ARP entry.
    740         @param mac_address: string MAC address associated with the new ARP
    741                 entry.
    742 
    743         """
    744         self.host.run('ip neigh add %s lladdr %s dev %s nud perm' %
    745                       (ip_address, mac_address, self.wifi_if))
    746 
    747 
    748     def discover_tdls_link(self, mac_address):
    749         """Send a TDLS Discover to |peer_mac_address|.
    750 
    751         @param mac_address: string MAC address associated with the TDLS peer.
    752 
    753         @return bool True if operation initiated successfully, False otherwise.
    754 
    755         """
    756         return self._shill_proxy.discover_tdls_link(self.wifi_if, mac_address)
    757 
    758 
    759     def establish_tdls_link(self, mac_address):
    760         """Establish a TDLS link with |mac_address|.
    761 
    762         @param mac_address: string MAC address associated with the TDLS peer.
    763 
    764         @return bool True if operation initiated successfully, False otherwise.
    765 
    766         """
    767         return self._shill_proxy.establish_tdls_link(self.wifi_if, mac_address)
    768 
    769 
    770     def query_tdls_link(self, mac_address):
    771         """Query a TDLS link with |mac_address|.
    772 
    773         @param mac_address: string MAC address associated with the TDLS peer.
    774 
    775         @return string indicating current TDLS connectivity.
    776 
    777         """
    778         return self._shill_proxy.query_tdls_link(self.wifi_if, mac_address)
    779 
    780 
    781     def add_wake_packet_source(self, source_ip):
    782         """Add |source_ip| as a source that can wake us up with packets.
    783 
    784         @param source_ip: IP address from which to wake upon receipt of packets
    785 
    786         @return True if successful, False otherwise.
    787 
    788         """
    789         return self._shill_proxy.add_wake_packet_source(
    790             self.wifi_if, source_ip)
    791 
    792 
    793     def remove_wake_packet_source(self, source_ip):
    794         """Remove |source_ip| as a source that can wake us up with packets.
    795 
    796         @param source_ip: IP address to stop waking on packets from
    797 
    798         @return True if successful, False otherwise.
    799 
    800         """
    801         return self._shill_proxy.remove_wake_packet_source(
    802             self.wifi_if, source_ip)
    803 
    804 
    805     def remove_all_wake_packet_sources(self):
    806         """Remove all IPs as sources that can wake us up with packets.
    807 
    808         @return True if successful, False otherwise.
    809 
    810         """
    811         return self._shill_proxy.remove_all_wake_packet_sources(self.wifi_if)
    812 
    813 
    814     def wake_on_wifi_features(self, features):
    815         """Shill supports programming the NIC to wake on special kinds of
    816         incoming packets, or on changes to the available APs (disconnect,
    817         coming in range of a known SSID). This method allows you to configure
    818         what wake-on-WiFi mechanisms are active. It returns a context manager,
    819         because this is a system-wide setting and we don't want it to persist
    820         across different tests.
    821 
    822         If you enable wake-on-packet, then the IPs registered by
    823         add_wake_packet_source will be able to wake the system from suspend.
    824 
    825         The correct way to use this method is:
    826 
    827         with client.wake_on_wifi_features(WAKE_ON_WIFI_DARKCONNECT):
    828             ...
    829 
    830         @param features: string from the WAKE_ON_WIFI constants above.
    831 
    832         @return a context manager for the features.
    833 
    834         """
    835         return TemporaryDBusProperty(self._shill_proxy,
    836                                      self.wifi_if,
    837                                      self.WAKE_ON_WIFI_FEATURES,
    838                                      features)
    839 
    840 
    841     def net_detect_scan_period_seconds(self, period):
    842         """Sets the period between net detect scans performed by the NIC to look
    843         for whitelisted SSIDs to |period|. This setting only takes effect if the
    844         NIC is programmed to wake on SSID. Use as with roam_threshold.
    845 
    846         @param period: integer number of seconds between NIC net detect scans
    847 
    848         @return a context manager for the net detect scan period
    849 
    850         """
    851         return TemporaryDBusProperty(self._shill_proxy,
    852                                      self.wifi_if,
    853                                      self.NET_DETECT_SCAN_PERIOD,
    854                                      period)
    855 
    856 
    857     def wake_to_scan_period_seconds(self, period):
    858         """Sets the period between RTC timer wakeups where the system is woken
    859         from suspend to perform scans. This setting only takes effect if the
    860         NIC is programmed to wake on SSID. Use as with roam_threshold.
    861 
    862         @param period: integer number of seconds between wake to scan RTC timer
    863                 wakes.
    864 
    865         @return a context manager for the net detect scan period
    866 
    867         """
    868         return TemporaryDBusProperty(self._shill_proxy,
    869                                      self.wifi_if,
    870                                      self.WAKE_TO_SCAN_PERIOD,
    871                                      period)
    872 
    873 
    874     def force_wake_to_scan_timer(self, is_forced):
    875         """Sets the boolean value determining whether or not to force the use of
    876         the wake to scan RTC timer, which wakes the system from suspend
    877         periodically to scan for networks.
    878 
    879         @param is_forced: boolean whether or not to force the use of the wake to
    880                 scan timer
    881 
    882         @return a context manager for the net detect scan period
    883 
    884         """
    885         return TemporaryDBusProperty(self._shill_proxy,
    886                                      self.wifi_if,
    887                                      self.FORCE_WAKE_TO_SCAN_TIMER,
    888                                      is_forced)
    889 
    890 
    891     def request_roam(self, bssid):
    892         """Request that we roam to the specified BSSID.
    893 
    894         Note that this operation assumes that:
    895 
    896         1) We're connected to an SSID for which |bssid| is a member.
    897         2) There is a BSS with an appropriate ID in our scan results.
    898 
    899         This method does not check for success of either the command or
    900         the roaming operation.
    901 
    902         @param bssid: string MAC address of bss to roam to.
    903 
    904         """
    905         self._wpa_cli_proxy.run_wpa_cli_cmd('roam %s' % bssid,
    906                                             check_result=False);
    907         return True
    908 
    909 
    910     def request_roam_dbus(self, bssid, interface):
    911         """Request that we roam to the specified BSSID through dbus.
    912 
    913         Note that this operation assumes that:
    914 
    915         1) We're connected to an SSID for which |bssid| is a member.
    916         2) There is a BSS with an appropriate ID in our scan results.
    917 
    918         @param bssid: string MAC address of bss to roam to.
    919 
    920         """
    921         self._assert_method_supported('request_roam_dbus')
    922         self._shill_proxy.request_roam_dbus(bssid, interface)
    923 
    924 
    925     def wait_for_roam(self, bssid, timeout_seconds=10.0):
    926         """Wait for a roam to the given |bssid|.
    927 
    928         @param bssid: string bssid to expect a roam to
    929                 (e.g.  '00:11:22:33:44:55').
    930         @param timeout_seconds: float number of seconds to wait for a roam.
    931         @return True iff we detect an association to the given |bssid| within
    932                 |timeout_seconds|.
    933 
    934         """
    935         start_time = time.time()
    936         success = False
    937         duration = 0.0
    938         while time.time() - start_time < timeout_seconds:
    939             duration = time.time() - start_time
    940             current_bssid = self.iw_runner.get_current_bssid(self.wifi_if)
    941             logging.debug('Current BSSID is %s.', current_bssid)
    942             if current_bssid == bssid:
    943                 success = True
    944                 break
    945             time.sleep(0.5)
    946 
    947         logging.debug('%s to %s in %f seconds.',
    948                       'Roamed ' if success else 'Failed to roam ',
    949                       bssid,
    950                       duration)
    951         return success
    952 
    953 
    954     def wait_for_ssid_vanish(self, ssid):
    955         """Wait for shill to notice that there are no BSS's for an SSID present.
    956 
    957         Raise a test failing exception if this does not come to pass.
    958 
    959         @param ssid: string SSID of the network to require be missing.
    960 
    961         """
    962         start_time = time.time()
    963         while time.time() - start_time < self.MAX_SERVICE_GONE_TIMEOUT_SECONDS:
    964             visible_ssids = self.get_active_wifi_SSIDs()
    965             logging.info('Got service list: %r', visible_ssids)
    966             if ssid not in visible_ssids:
    967                 return
    968 
    969             self.scan(frequencies=[], ssids=[], timeout_seconds=30)
    970         else:
    971             raise error.TestFail('shill should mark the BSS as not present')
    972 
    973 
    974     def reassociate(self, timeout_seconds=10):
    975         """Reassociate to the connected network.
    976 
    977         @param timeout_seconds: float number of seconds to wait for operation
    978                 to complete.
    979 
    980         """
    981         logging.info('Attempt to reassociate')
    982         with self.iw_runner.get_event_logger() as logger:
    983             logger.start()
    984             # Issue reattach command to wpa_supplicant
    985             self._wpa_cli_proxy.run_wpa_cli_cmd('reattach');
    986 
    987             # Wait for the timeout seconds for association to complete
    988             time.sleep(timeout_seconds)
    989 
    990             # Stop iw event logger
    991             logger.stop()
    992 
    993             # Get association time based on the iw event log
    994             reassociate_time = logger.get_reassociation_time()
    995             if reassociate_time is None or reassociate_time > timeout_seconds:
    996                 raise error.TestFail(
    997                         'Failed to reassociate within given timeout')
    998             logging.info('Reassociate time: %.2f seconds', reassociate_time)
    999 
   1000 
   1001     def wait_for_connection(self, ssid, timeout_seconds=30, freq=None,
   1002                             ping_ip=None, desired_subnet=None):
   1003         """Verifies a connection to network ssid, optionally verifying
   1004         frequency, ping connectivity and subnet.
   1005 
   1006         @param ssid string ssid of the network to check.
   1007         @param timeout_seconds int number of seconds to wait for
   1008                 connection on the given frequency.
   1009         @param freq int frequency of network to check.
   1010         @param ping_ip string ip address to ping for verification.
   1011         @param desired_subnet string expected subnet in which client
   1012                 ip address should reside.
   1013 
   1014         @returns a named tuple of (state, time)
   1015         """
   1016         POLLING_INTERVAL_SECONDS = 1.0
   1017         start_time = time.time()
   1018         duration = lambda: time.time() - start_time
   1019         success = False
   1020         while duration() < timeout_seconds:
   1021             success, state, conn_time  = self.wait_for_service_states(
   1022                     ssid, self.CONNECTED_STATES,
   1023                     int(math.ceil(timeout_seconds - duration())))
   1024             if not success:
   1025                 time.sleep(POLLING_INTERVAL_SECONDS)
   1026                 continue
   1027 
   1028             if freq:
   1029                 actual_freq = self.get_iw_link_value(
   1030                         iw_runner.IW_LINK_KEY_FREQUENCY)
   1031                 if str(freq) != actual_freq:
   1032                     logging.debug('Waiting for desired frequency %s (got %s).',
   1033                                   freq, actual_freq)
   1034                     time.sleep(POLLING_INTERVAL_SECONDS)
   1035                     continue
   1036 
   1037             if desired_subnet:
   1038                 actual_subnet = self.wifi_ip_subnet
   1039                 if actual_subnet != desired_subnet:
   1040                     logging.debug('Waiting for desired subnet %s (got %s).',
   1041                                   desired_subnet, actual_subnet)
   1042                     time.sleep(POLLING_INTERVAL_SECONDS)
   1043                     continue
   1044 
   1045             if ping_ip:
   1046                 ping_config = ping_runner.PingConfig(ping_ip)
   1047                 self.ping(ping_config)
   1048 
   1049             return ConnectTime(state, conn_time)
   1050 
   1051         freq_error_str = (' on frequency %d Mhz' % freq) if freq else ''
   1052         raise error.TestFail(
   1053                 'Failed to connect to "%s"%s in %f seconds (state=%s)' %
   1054                 (ssid, freq_error_str, duration(), state))
   1055 
   1056 
   1057     @contextmanager
   1058     def assert_disconnect_count(self, count):
   1059         """Context asserting |count| disconnects for the context lifetime.
   1060 
   1061         Creates an iw logger during the lifetime of the context and asserts
   1062         that the client disconnects exactly |count| times.
   1063 
   1064         @param count int the expected number of disconnections.
   1065 
   1066         """
   1067         with self.iw_runner.get_event_logger() as logger:
   1068             logger.start()
   1069             yield
   1070             logger.stop()
   1071             if logger.get_disconnect_count() != count:
   1072                 raise error.TestFail(
   1073                     'Client disconnected %d times; expected %d' %
   1074                     (logger.get_disconnect_count(), count))
   1075 
   1076 
   1077     def assert_no_disconnects(self):
   1078         """Context asserting no disconnects for the context lifetime."""
   1079         return self.assert_disconnect_count(0)
   1080 
   1081 
   1082     @contextmanager
   1083     def assert_disconnect_event(self):
   1084         """Context asserting at least one disconnect for the context lifetime.
   1085 
   1086         Creates an iw logger during the lifetime of the context and asserts
   1087         that the client disconnects at least one time.
   1088 
   1089         """
   1090         with self.iw_runner.get_event_logger() as logger:
   1091             logger.start()
   1092             yield
   1093             logger.stop()
   1094             if logger.get_disconnect_count() == 0:
   1095                 raise error.TestFail('Client did not disconnect')
   1096 
   1097 
   1098     def get_num_card_resets(self):
   1099         """Get card reset count."""
   1100         reset_msg = 'mwifiex_sdio_card_reset'
   1101         result = self.host.run('grep -c %s /var/log/messages' % reset_msg,
   1102                                ignore_status=True)
   1103         if result.exit_status == 1:
   1104             return 0
   1105         count = int(result.stdout.strip())
   1106         return count
   1107 
   1108 
   1109     def get_disconnect_reasons(self):
   1110         """Get disconnect reason codes."""
   1111         disconnect_reason_msg = "updated DisconnectReason to ";
   1112         disconnect_reason_cleared = "clearing DisconnectReason for ";
   1113         result = self.host.run('grep -E "(%s|%s)" /var/log/net.log' %
   1114                                (disconnect_reason_msg,
   1115                                disconnect_reason_cleared),
   1116                                ignore_status=True)
   1117         if result.exit_status == 1:
   1118             return None
   1119 
   1120         lines = result.stdout.strip().split('\n')
   1121         disconnect_reasons = []
   1122         disconnect_reason_regex = re.compile(' to (\D?\d+)')
   1123 
   1124         found = False
   1125         for line in reversed(lines):
   1126           match = disconnect_reason_regex.search(line)
   1127           if match is not None:
   1128             disconnect_reasons.append(match.group(1))
   1129             found = True
   1130           else:
   1131             if (found):
   1132                 break
   1133         return list(reversed(disconnect_reasons))
   1134 
   1135 
   1136     def release_wifi_if(self):
   1137         """Release the control over the wifi interface back to normal operation.
   1138 
   1139         This will give the ownership of the wifi interface back to shill and
   1140         wpa_supplicant.
   1141 
   1142         """
   1143         self.set_device_enabled(self._wifi_if, True)
   1144 
   1145 
   1146     def claim_wifi_if(self):
   1147         """Claim the control over the wifi interface from this wifi client.
   1148 
   1149         This claim the ownership of the wifi interface from shill and
   1150         wpa_supplicant. The wifi interface should be UP when this call returns.
   1151 
   1152         """
   1153         # Disabling a wifi device in shill will remove that device from
   1154         # wpa_supplicant as well.
   1155         self.set_device_enabled(self._wifi_if, False)
   1156 
   1157         # Wait for shill to bring down the wifi interface.
   1158         is_interface_down = lambda: not self._interface.is_up
   1159         client_site_utils.poll_for_condition(
   1160                 is_interface_down,
   1161                 timeout=INTERFACE_DOWN_WAIT_TIME_SECONDS,
   1162                 sleep_interval=0.5,
   1163                 desc='Timeout waiting for interface to go down.')
   1164         # Bring up the wifi interface to allow the test to use the interface.
   1165         self.host.run('%s link set %s up' % (self.cmd_ip, self.wifi_if))
   1166 
   1167 
   1168     def set_sched_scan(self, enable, fail_on_unsupported=False):
   1169         """enable/disable scheduled scan.
   1170 
   1171         @param enable bool flag indicating to enable/disable scheduled scan.
   1172 
   1173         """
   1174         if fail_on_unsupported:
   1175             self._assert_method_supported('set_sched_scan')
   1176         elif not self._supports_method('set_sched_scan'):
   1177             return False
   1178         return self._shill_proxy.set_sched_scan(enable)
   1179 
   1180 
   1181     def check_connected_on_last_resume(self):
   1182         """Checks whether the DUT was connected on its last resume.
   1183 
   1184         Checks that the DUT was connected after waking from suspend by parsing
   1185         the last instance shill log message that reports shill's connection
   1186         status on resume. Fails the test if this log message reports that
   1187         the DUT woke up disconnected.
   1188 
   1189         """
   1190         # As of build R43 6913.0.0, the shill log message from the function
   1191         # OnAfterResume is called as soon as shill resumes from suspend, and
   1192         # will report whether or not shill is connected. The log message will
   1193         # take one of the following two forms:
   1194         #
   1195         #       [...] [INFO:wifi.cc(1941)] OnAfterResume: connected
   1196         #       [...] [INFO:wifi.cc(1941)] OnAfterResume: not connected
   1197         #
   1198         # where 1941 is an arbitrary PID number. By checking if the last
   1199         # instance of this message contains the substring "not connected", we
   1200         # can determine whether or not shill was connected on its last resume.
   1201         connection_status_log_regex_str = 'INFO:wifi\.cc.*OnAfterResume'
   1202         not_connected_substr = 'not connected'
   1203         connected_substr = 'connected'
   1204 
   1205         cmd = ('grep -E %s /var/log/net.log | tail -1' %
   1206                connection_status_log_regex_str)
   1207         connection_status_log = self.host.run(cmd).stdout
   1208         if not connection_status_log:
   1209             raise error.TestFail('Could not find resume connection status log '
   1210                                  'message.')
   1211 
   1212         logging.debug('Connection status message:\n%s', connection_status_log)
   1213         if not_connected_substr in connection_status_log:
   1214             raise error.TestFail('Client was not connected upon waking from '
   1215                                  'suspend.')
   1216 
   1217         if not connected_substr in connection_status_log:
   1218             raise error.TestFail('Last resume log message did not contain '
   1219                                  'connection status.')
   1220 
   1221         logging.info('Client was connected upon waking from suspend.')
   1222 
   1223 
   1224     def check_wake_on_wifi_throttled(self):
   1225         """
   1226         Checks whether wake on WiFi was throttled on the DUT on the last dark
   1227         resume. Check for this by parsing shill logs for a throttling message.
   1228 
   1229         """
   1230         # We are looking for an dark resume error log message indicating that
   1231         # wake on WiFi was throttled. This is an example of the error message:
   1232         #     [...] [ERROR:wake_on_wifi.cc(1304)] OnDarkResume: Too many dark \
   1233         #       resumes; disabling wake on WiFi temporarily
   1234         dark_resume_log_regex_str = 'ERROR:wake_on_wifi\.cc.*OnDarkResume:.*'
   1235         throttled_msg_substr = ('Too many dark resumes; disabling wake on '
   1236                                    'WiFi temporarily')
   1237 
   1238         cmd = ('grep -E %s /var/log/net.log | tail -1' %
   1239                dark_resume_log_regex_str)
   1240         last_dark_resume_error_log = self.host.run(cmd).stdout
   1241         if not last_dark_resume_error_log:
   1242             raise error.TestFail('Could not find a dark resume log message.')
   1243 
   1244         logging.debug('Last dark resume log message:\n%s',
   1245                 last_dark_resume_error_log)
   1246         if not throttled_msg_substr in last_dark_resume_error_log:
   1247             raise error.TestFail('Wake on WiFi was not throttled on the last '
   1248                                  'dark resume.')
   1249 
   1250         logging.info('Wake on WiFi was throttled on the last dark resume.')
   1251 
   1252 
   1253     def shill_debug_log(self, message):
   1254         """Logs a message to the shill log (i.e. /var/log/net.log). This
   1255            message will be logged at the DEBUG level.
   1256 
   1257         @param message: the message to be logged.
   1258 
   1259         """
   1260         logging.info(message)
   1261 
   1262         # Skip this command if running on Android, since Android does not
   1263         # have a /usr/bin/logger (or an equivalent that takes the same
   1264         # parameters as the Linux logger does.)
   1265         if not isinstance(self.host, adb_host.ADBHost):
   1266             logger_command = ('/usr/bin/logger'
   1267                               ' --tag shill'
   1268                               ' --priority daemon.debug'
   1269                               ' "%s"' % base_utils.sh_escape(message))
   1270             self.host.run(logger_command)
   1271 
   1272 
   1273     def is_wake_on_wifi_supported(self):
   1274         """Returns true iff wake-on-WiFi is supported by the DUT."""
   1275 
   1276         if (self.shill.get_dbus_property_on_device(
   1277                     self.wifi_if, self.WAKE_ON_WIFI_FEATURES) ==
   1278              WAKE_ON_WIFI_NOT_SUPPORTED):
   1279             return False
   1280         return True
   1281 
   1282 
   1283 class TemporaryDBusProperty:
   1284     """Utility class to temporarily change a dbus property for the WiFi device.
   1285 
   1286     Since dbus properties are global and persistent settings, we want
   1287     to make sure that we change them back to what they were before the test
   1288     started.
   1289 
   1290     """
   1291 
   1292     def __init__(self, shill_proxy, interface, prop_name, value):
   1293         """Construct a TemporaryDBusProperty context manager.
   1294 
   1295         @param shill_proxy: the shill proxy to use to communicate via dbus
   1296         @param interface: the name of the interface we are setting the property for
   1297         @param prop_name: the name of the property we want to set
   1298         @param value: the desired value of the property
   1299 
   1300         """
   1301         self._shill = shill_proxy
   1302         self._interface = interface
   1303         self._prop_name = prop_name
   1304         self._value = value
   1305         self._saved_value = None
   1306 
   1307 
   1308     def __enter__(self):
   1309         logging.info('- Setting property %s on device %s',
   1310                 self._prop_name, self._interface)
   1311 
   1312         self._saved_value = self._shill.get_dbus_property_on_device(
   1313                 self._interface, self._prop_name)
   1314         if self._saved_value is None:
   1315             raise error.TestFail('Device or property not found.')
   1316         if not self._shill.set_dbus_property_on_device(self._interface,
   1317                                                        self._prop_name,
   1318                                                        self._value):
   1319             raise error.TestFail('Could not set property')
   1320 
   1321         logging.info('- Changed value from %s to %s',
   1322                      self._saved_value,
   1323                      self._value)
   1324 
   1325 
   1326     def __exit__(self, exception, value, traceback):
   1327         logging.info('- Resetting property %s', self._prop_name)
   1328 
   1329         if not self._shill.set_dbus_property_on_device(self._interface,
   1330                                                        self._prop_name,
   1331                                                        self._saved_value):
   1332             raise error.TestFail('Could not reset property')
   1333