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