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