• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2013 The Chromium 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 collections
6import logging
7import operator
8import re
9
10from autotest_lib.client.common_lib import error
11from autotest_lib.client.common_lib import utils
12from autotest_lib.client.common_lib.cros.network import iw_event_logger
13
14# These must mirror the values in 'iw list' output.
15CHAN_FLAG_DISABLED = 'disabled'
16CHAN_FLAG_NO_IR = 'no IR'
17CHAN_FLAG_PASSIVE_SCAN = 'passive scan'
18CHAN_FLAG_RADAR_DETECT = 'radar detection'
19DEV_MODE_AP = 'AP'
20DEV_MODE_IBSS = 'IBSS'
21DEV_MODE_MONITOR = 'monitor'
22DEV_MODE_MESH_POINT = 'mesh point'
23DEV_MODE_STATION = 'managed'
24SUPPORTED_DEV_MODES = (DEV_MODE_AP, DEV_MODE_IBSS, DEV_MODE_MONITOR,
25                       DEV_MODE_MESH_POINT, DEV_MODE_STATION)
26
27HT20 = 'HT20'
28HT40_ABOVE = 'HT40+'
29HT40_BELOW = 'HT40-'
30
31SECURITY_OPEN = 'open'
32SECURITY_WEP = 'wep'
33SECURITY_WPA = 'wpa'
34SECURITY_WPA2 = 'wpa2'
35# Mixed mode security is WPA2/WPA
36SECURITY_MIXED = 'mixed'
37
38# Table of lookups between the output of item 'secondary channel offset:' from
39# iw <device> scan to constants.
40
41HT_TABLE = {'no secondary': HT20,
42            'above': HT40_ABOVE,
43            'below': HT40_BELOW}
44
45IwBand = collections.namedtuple(
46    'Band', ['num', 'frequencies', 'frequency_flags', 'mcs_indices'])
47IwBss = collections.namedtuple('IwBss', ['bss', 'frequency', 'ssid', 'security',
48                                         'ht', 'signal'])
49IwNetDev = collections.namedtuple('IwNetDev', ['phy', 'if_name', 'if_type'])
50IwTimedScan = collections.namedtuple('IwTimedScan', ['time', 'bss_list'])
51
52# The fields for IwPhy are as follows:
53#   name: string name of the phy, such as "phy0"
54#   bands: list of IwBand objects.
55#   modes: List of strings containing interface modes supported, such as "AP".
56#   commands: List of strings containing nl80211 commands supported, such as
57#          "authenticate".
58#   features: List of strings containing nl80211 features supported, such as
59#          "T-DLS".
60#   max_scan_ssids: Maximum number of SSIDs which can be scanned at once.
61IwPhy = collections.namedtuple(
62    'Phy', ['name', 'bands', 'modes', 'commands', 'features',
63            'max_scan_ssids', 'avail_tx_antennas', 'avail_rx_antennas',
64            'supports_setting_antenna_mask', 'support_vht'])
65
66DEFAULT_COMMAND_IW = 'iw'
67
68# Redirect stderr to stdout on Cros since adb commands cannot distinguish them
69# on Brillo.
70IW_TIME_COMMAND_FORMAT = '(time -p %s) 2>&1'
71IW_TIME_COMMAND_OUTPUT_START = 'real'
72
73IW_LINK_KEY_BEACON_INTERVAL = 'beacon int'
74IW_LINK_KEY_DTIM_PERIOD = 'dtim period'
75IW_LINK_KEY_FREQUENCY = 'freq'
76IW_LINK_KEY_SIGNAL = 'signal'
77IW_LINK_KEY_RX_BITRATE = 'rx bitrate'
78IW_LINK_KEY_RX_DROPS = 'rx drop misc'
79IW_LINK_KEY_RX_PACKETS = 'rx packets'
80IW_LINK_KEY_TX_BITRATE = 'tx bitrate'
81IW_LINK_KEY_TX_FAILURES = 'tx failed'
82IW_LINK_KEY_TX_PACKETS = 'tx packets'
83IW_LINK_KEY_TX_RETRIES = 'tx retries'
84IW_LOCAL_EVENT_LOG_FILE = './debug/iw_event_%d.log'
85
86
87def _get_all_link_keys(link_information):
88    """Parses link or station dump output for link key value pairs.
89
90    Link or station dump information is in the format below:
91
92    Connected to 74:e5:43:10:4f:c0 (on wlan0)
93          SSID: PMKSACaching_4m9p5_ch1
94          freq: 5220
95          RX: 5370 bytes (37 packets)
96          TX: 3604 bytes (15 packets)
97          signal: -59 dBm
98          tx bitrate: 13.0 MBit/s MCS 1
99
100          bss flags:      short-slot-time
101          dtim period:    5
102          beacon int:     100
103
104    @param link_information: string containing the raw link or station dump
105        information as reported by iw. Note that this parsing assumes a single
106        entry, in the case of multiple entries (e.g. listing stations from an
107        AP, or listing mesh peers), the entries must be split on a per
108        peer/client basis before this parsing operation.
109    @return a dictionary containing all the link key/value pairs.
110
111    """
112    link_key_value_pairs = {}
113    keyval_regex = re.compile(r'^\s+(.*):\s+(.*)$')
114    for link_key in link_information.splitlines()[1:]:
115        match = re.search(keyval_regex, link_key)
116        if match:
117            # Station dumps can contain blank lines.
118            link_key_value_pairs[match.group(1)] = match.group(2)
119    return link_key_value_pairs
120
121
122def _extract_bssid(link_information, interface_name, station_dump=False):
123    """Get the BSSID that |interface_name| is associated with.
124
125    See doc for _get_all_link_keys() for expected format of the station or link
126    information entry.
127
128    @param link_information: string containing the raw link or station dump
129        information as reported by iw. Note that this parsing assumes a single
130        entry, in the case of multiple entries (e.g. listing stations from an AP
131        or listing mesh peers), the entries must be split on a per peer/client
132        basis before this parsing operation.
133    @param interface_name: string name of interface (e.g. 'wlan0').
134    @param station_dump: boolean indicator of whether the link information is
135        from a 'station dump' query. If False, it is assumed the string is from
136        a 'link' query.
137    @return string bssid of the current association, or None if no matching
138        association information is found.
139
140    """
141    # We're looking for a line like this when parsing the output of a 'link'
142    # query:
143    #   Connected to 04:f0:21:03:7d:bb (on wlan0)
144    # We're looking for a line like this when parsing the output of a
145    # 'station dump' query:
146    #   Station 04:f0:21:03:7d:bb (on mesh-5000mhz)
147    identifier = 'Station' if station_dump else 'Connected to'
148    search_re = r'%s ([0-9a-fA-F:]{17}) \(on %s\)' % (identifier,
149                                                      interface_name)
150    match = re.match(search_re, link_information)
151    if match is None:
152        return None
153    return match.group(1)
154
155
156class IwRunner(object):
157    """Defines an interface to the 'iw' command."""
158
159
160    def __init__(self, remote_host=None, command_iw=DEFAULT_COMMAND_IW):
161        self._run = utils.run
162        self._host = remote_host
163        if remote_host:
164            self._run = remote_host.run
165        self._command_iw = command_iw
166        self._log_id = 0
167
168
169    def _parse_scan_results(self, output):
170        """Parse the output of the 'scan' and 'scan dump' commands.
171
172        Here is an example of what a single network would look like for
173        the input parameter.  Some fields have been removed in this example:
174          BSS 00:11:22:33:44:55(on wlan0)
175          freq: 2447
176          beacon interval: 100 TUs
177          signal: -46.00 dBm
178          Information elements from Probe Response frame:
179          SSID: my_open_network
180          Extended supported rates: 24.0 36.0 48.0 54.0
181          HT capabilities:
182          Capabilities: 0x0c
183          HT20
184          HT operation:
185          * primary channel: 8
186          * secondary channel offset: no secondary
187          * STA channel width: 20 MHz
188          RSN: * Version: 1
189          * Group cipher: CCMP
190          * Pairwise ciphers: CCMP
191          * Authentication suites: PSK
192          * Capabilities: 1-PTKSA-RC 1-GTKSA-RC (0x0000)
193
194        @param output: string command output.
195
196        @returns a list of IwBss namedtuples; None if the scan fails
197
198        """
199        bss = None
200        frequency = None
201        ssid = None
202        ht = None
203        signal = None
204        security = None
205        supported_securities = []
206        bss_list = []
207        for line in output.splitlines():
208            line = line.strip()
209            bss_match = re.match('BSS ([0-9a-f:]+)', line)
210            if bss_match:
211                if bss != None:
212                    security = self.determine_security(supported_securities)
213                    iwbss = IwBss(bss, frequency, ssid, security, ht, signal)
214                    bss_list.append(iwbss)
215                    bss = frequency = ssid = security = ht = None
216                    supported_securities = []
217                bss = bss_match.group(1)
218            if line.startswith('freq:'):
219                frequency = int(line.split()[1])
220            if line.startswith('signal:'):
221                signal = float(line.split()[1])
222            if line.startswith('SSID: '):
223                _, ssid = line.split(': ', 1)
224            if line.startswith('* secondary channel offset'):
225                ht = HT_TABLE[line.split(':')[1].strip()]
226            if line.startswith('WPA'):
227               supported_securities.append(SECURITY_WPA)
228            if line.startswith('RSN'):
229               supported_securities.append(SECURITY_WPA2)
230        security = self.determine_security(supported_securities)
231        bss_list.append(IwBss(bss, frequency, ssid, security, ht, signal))
232        return bss_list
233
234
235    def _parse_scan_time(self, output):
236        """
237        Parse the scan time in seconds from the output of the 'time -p "scan"'
238        command.
239
240        'time -p' Command output format is below:
241        real     0.01
242        user     0.01
243        sys      0.00
244
245        @param output: string command output.
246
247        @returns float time in seconds.
248
249        """
250        output_lines = output.splitlines()
251        for line_num, line in enumerate(output_lines):
252            line = line.strip()
253            if (line.startswith(IW_TIME_COMMAND_OUTPUT_START) and
254                output_lines[line_num + 1].startswith('user') and
255                output_lines[line_num + 2].startswith('sys')):
256                return float(line.split()[1])
257        raise error.TestFail('Could not parse scan time.')
258
259
260    def add_interface(self, phy, interface, interface_type):
261        """
262        Add an interface to a WiFi PHY.
263
264        @param phy: string name of PHY to add an interface to.
265        @param interface: string name of interface to add.
266        @param interface_type: string type of interface to add (e.g. 'monitor').
267
268        """
269        self._run('%s phy %s interface add %s type %s' %
270                  (self._command_iw, phy, interface, interface_type))
271
272
273    def disconnect_station(self, interface):
274        """
275        Disconnect a STA from a network.
276
277        @param interface: string name of interface to disconnect.
278
279        """
280        self._run('%s dev %s disconnect' % (self._command_iw, interface))
281
282
283    def get_current_bssid(self, interface_name):
284        """Get the BSSID that |interface_name| is associated with.
285
286        @param interface_name: string name of interface (e.g. 'wlan0').
287        @return string bssid of our current association, or None.
288
289        """
290        result = self._run('%s dev %s link' %
291                           (self._command_iw, interface_name),
292                           ignore_status=True)
293        if result.exit_status:
294            # See comment in get_link_value.
295            return None
296
297        return _extract_bssid(result.stdout, interface_name)
298
299
300    def get_interface(self, interface_name):
301        """Get full information about an interface given an interface name.
302
303        @param interface_name: string name of interface (e.g. 'wlan0').
304        @return IwNetDev tuple.
305
306        """
307        matching_interfaces = [iw_if for iw_if in self.list_interfaces()
308                                     if iw_if.if_name == interface_name]
309        if len(matching_interfaces) != 1:
310            raise error.TestFail('Could not find interface named %s' %
311                                 interface_name)
312
313        return matching_interfaces[0]
314
315
316    def get_link_value(self, interface, iw_link_key):
317        """Get the value of a link property for |interface|.
318
319        Checks the link using iw, and parses the result to return a link key.
320
321        @param iw_link_key: string one of IW_LINK_KEY_* defined above.
322        @param interface: string desired value of iw link property.
323        @return string containing the corresponding link property value, None
324            if there was a parsing error or the iw command failed.
325
326        """
327        result = self._run('%s dev %s link' % (self._command_iw, interface),
328                           ignore_status=True)
329        if result.exit_status:
330            # When roaming, there is a period of time for mac80211 based drivers
331            # when the driver is 'associated' with an SSID but not a particular
332            # BSS.  This causes iw to return an error code (-2) when attempting
333            # to retrieve information specific to the BSS.  This does not happen
334            # in mwifiex drivers.
335            return None
336        actual_value = _get_all_link_keys(result.stdout).get(iw_link_key)
337        if actual_value is not None:
338            logging.info('Found iw link key %s with value %s.',
339                         iw_link_key, actual_value)
340        return actual_value
341
342
343    def get_station_dump(self, interface):
344        """Gets information about connected peers.
345
346        Returns information about the currently connected peers. When the host
347        is in station mode, it returns a single entry, with information about
348        the link to the AP it is currently connected to. If the host is in mesh
349        or AP mode, it can return multiple entries, one for each connected
350        station, or mesh peer.
351
352        @param interface: string name of interface to get peer information
353            from.
354        @return a list of dictionaries with link information about each
355            connected peer (ordered by peer mac address).
356
357        """
358        result = self._run('%s dev %s station dump' %
359                           (self._command_iw, interface))
360        parts = re.split(r'^Station ', result.stdout, flags=re.MULTILINE)[1:]
361        peer_list_raw = ['Station ' + x for x in parts]
362        parsed_peer_info = []
363
364        for peer in peer_list_raw:
365            peer_link_keys = _get_all_link_keys(peer)
366            rssi_str = peer_link_keys.get(IW_LINK_KEY_SIGNAL, '0')
367            rssi_int = int(rssi_str.split()[0])
368
369            tx_bitrate = peer_link_keys.get(IW_LINK_KEY_TX_BITRATE, '0')
370            tx_failures = int(peer_link_keys.get(IW_LINK_KEY_TX_FAILURES, 0))
371            tx_packets = int(peer_link_keys.get(IW_LINK_KEY_TX_PACKETS, 0))
372            tx_retries = int(peer_link_keys.get(IW_LINK_KEY_TX_RETRIES, 0))
373
374            rx_bitrate = peer_link_keys.get(IW_LINK_KEY_RX_BITRATE, '0')
375            rx_drops = int(peer_link_keys.get(IW_LINK_KEY_RX_DROPS, 0))
376            rx_packets = int(peer_link_keys.get(IW_LINK_KEY_RX_PACKETS, 0))
377
378            mac = _extract_bssid(link_information=peer,
379                                 interface_name=interface,
380                                 station_dump=True)
381
382            # If any of these are missing, they will be None
383            peer_info = {'rssi_int': rssi_int,
384                         'rssi_str': rssi_str,
385                         'tx_bitrate': tx_bitrate,
386                         'tx_failures': tx_failures,
387                         'tx_packets': tx_packets,
388                         'tx_retries': tx_retries,
389                         'rx_bitrate': rx_bitrate,
390                         'rx_drops': rx_drops,
391                         'rx_packets': rx_packets,
392                         'mac': mac}
393
394            # don't evaluate if tx_packets 0
395            if tx_packets:
396                peer_info['tx_retry_rate'] = tx_retries / float(tx_packets)
397                peer_info['tx_failure_rate'] =  tx_failures / float(tx_packets)
398
399            # don't evaluate if rx_packets is 0
400            if rx_packets:
401                peer_info['rx_drop_rate'] = rx_drops / float(rx_packets)
402
403            parsed_peer_info.append(peer_info)
404        return sorted(parsed_peer_info, key=operator.itemgetter('mac'))
405
406
407    def get_operating_mode(self, interface):
408        """Gets the operating mode for |interface|.
409
410        @param interface: string name of interface to get peer information
411            about.
412
413        @return string one of DEV_MODE_* defined above, or None if no mode is
414            found, or if an unsupported mode is found.
415
416        """
417        ret = self._run('%s dev %s info' % (self._command_iw, interface))
418        mode_regex = r'^\s*type (.*)$'
419        match = re.search(mode_regex, ret.stdout, re.MULTILINE)
420        if match:
421            operating_mode = match.group(1)
422            if operating_mode in SUPPORTED_DEV_MODES:
423                return operating_mode
424            logging.warning(
425                'Unsupported operating mode %s found for interface: %s. '
426                'Supported modes: %s', operating_mode, interface,
427                SUPPORTED_DEV_MODES)
428        return None
429
430
431    def get_radio_config(self, interface):
432        """Gets the channel information of a specfic interface using iw.
433
434        @param interface: string name of interface to get radio information
435            from.
436
437        @return dictionary containing the channel information.
438
439        """
440        channel_config = {}
441        ret = self._run('%s dev %s info' % (self._command_iw, interface))
442        channel_config_regex = (r'^\s*channel ([0-9]+) \(([0-9]+) MHz\), '
443                                 'width: ([2,4,8]0) MHz, center1: ([0-9]+) MHz')
444        match = re.search(channel_config_regex, ret.stdout, re.MULTILINE)
445
446        if match:
447            channel_config['number'] = int(match.group(1))
448            channel_config['freq'] = int(match.group(2))
449            channel_config['width'] = int(match.group(3))
450            channel_config['center1_freq'] = int(match.group(4))
451
452        return channel_config
453
454
455    def ibss_join(self, interface, ssid, frequency):
456        """
457        Join a WiFi interface to an IBSS.
458
459        @param interface: string name of interface to join to the IBSS.
460        @param ssid: string SSID of IBSS to join.
461        @param frequency: int frequency of IBSS in Mhz.
462
463        """
464        self._run('%s dev %s ibss join %s %d' %
465                  (self._command_iw, interface, ssid, frequency))
466
467
468    def ibss_leave(self, interface):
469        """
470        Leave an IBSS.
471
472        @param interface: string name of interface to remove from the IBSS.
473
474        """
475        self._run('%s dev %s ibss leave' % (self._command_iw, interface))
476
477
478    def list_interfaces(self, desired_if_type=None):
479        """List WiFi related interfaces on this system.
480
481        @param desired_if_type: string type of interface to filter
482                our returned list of interfaces for (e.g. 'managed').
483
484        @return list of IwNetDev tuples.
485
486        """
487
488        # Parse output in the following format:
489        #
490        #   $ adb shell iw dev
491        #   phy#0
492        #     Unnamed/non-netdev interface
493        #       wdev 0x2
494        #       addr aa:bb:cc:dd:ee:ff
495        #       type P2P-device
496        #     Interface wlan0
497        #       ifindex 4
498        #       wdev 0x1
499        #       addr aa:bb:cc:dd:ee:ff
500        #       ssid Whatever
501        #       type managed
502
503        output = self._run('%s dev' % self._command_iw).stdout
504        interfaces = []
505        phy = None
506        if_name = None
507        if_type = None
508        for line in output.splitlines():
509            m = re.match('phy#([0-9]+)', line)
510            if m:
511                phy = 'phy%d' % int(m.group(1))
512                if_name = None
513                if_type = None
514                continue
515            if not phy:
516                continue
517            m = re.match('[\s]*Interface (.*)', line)
518            if m:
519                if_name = m.group(1)
520                continue
521            if not if_name:
522                continue
523            # Common values for type are 'managed', 'monitor', and 'IBSS'.
524            m = re.match('[\s]*type ([a-zA-Z]+)', line)
525            if m:
526                if_type = m.group(1)
527                interfaces.append(IwNetDev(phy=phy, if_name=if_name,
528                                           if_type=if_type))
529                # One phy may have many interfaces, so don't reset it.
530                if_name = None
531
532        if desired_if_type:
533            interfaces = [interface for interface in interfaces
534                          if interface.if_type == desired_if_type]
535        return interfaces
536
537
538    def list_phys(self):
539        """
540        List WiFi PHYs on the given host.
541
542        @return list of IwPhy tuples.
543
544        """
545        output = self._run('%s list' % self._command_iw).stdout
546
547        pending_phy_name = None
548        current_band = None
549        current_section = None
550        all_phys = []
551
552        def add_pending_phy():
553            """Add the pending phy into |all_phys|."""
554            bands = tuple(IwBand(band.num,
555                                 tuple(band.frequencies),
556                                 dict(band.frequency_flags),
557                                 tuple(band.mcs_indices))
558                          for band in pending_phy_bands)
559            new_phy = IwPhy(pending_phy_name,
560                            bands,
561                            tuple(pending_phy_modes),
562                            tuple(pending_phy_commands),
563                            tuple(pending_phy_features),
564                            pending_phy_max_scan_ssids,
565                            pending_phy_tx_antennas,
566                            pending_phy_rx_antennas,
567                            pending_phy_tx_antennas and pending_phy_rx_antennas,
568                            pending_phy_support_vht)
569            all_phys.append(new_phy)
570
571        for line in output.splitlines():
572            match_phy = re.search('Wiphy (.*)', line)
573            if match_phy:
574                if pending_phy_name:
575                    add_pending_phy()
576                pending_phy_name = match_phy.group(1)
577                pending_phy_bands = []
578                pending_phy_modes = []
579                pending_phy_commands = []
580                pending_phy_features = []
581                pending_phy_max_scan_ssids = None
582                pending_phy_tx_antennas = 0
583                pending_phy_rx_antennas = 0
584                pending_phy_support_vht = False
585                continue
586
587            match_section = re.match('\s*(\w.*):\s*$', line)
588            if match_section:
589                current_section = match_section.group(1)
590                match_band = re.match('Band (\d+)', current_section)
591                if match_band:
592                    current_band = IwBand(num=int(match_band.group(1)),
593                                          frequencies=[],
594                                          frequency_flags={},
595                                          mcs_indices=[])
596                    pending_phy_bands.append(current_band)
597                continue
598
599            # Check for max_scan_ssids. This isn't a section, but it
600            # also isn't within a section.
601            match_max_scan_ssids = re.match('\s*max # scan SSIDs: (\d+)',
602                                            line)
603            if match_max_scan_ssids and pending_phy_name:
604                pending_phy_max_scan_ssids = int(
605                    match_max_scan_ssids.group(1))
606                continue
607
608            if (current_section == 'Supported interface modes' and
609                pending_phy_name):
610                mode_match = re.search('\* (\w+)', line)
611                if mode_match:
612                    pending_phy_modes.append(mode_match.group(1))
613                    continue
614
615            if current_section == 'Supported commands' and pending_phy_name:
616                command_match = re.search('\* (\w+)', line)
617                if command_match:
618                    pending_phy_commands.append(command_match.group(1))
619                    continue
620
621            if (current_section is not None and
622                current_section.startswith('VHT Capabilities') and
623                pending_phy_name):
624                pending_phy_support_vht = True
625                continue
626
627            match_avail_antennas = re.match('\s*Available Antennas: TX (\S+)'
628                                            ' RX (\S+)', line)
629            if match_avail_antennas and pending_phy_name:
630                pending_phy_tx_antennas = int(
631                        match_avail_antennas.group(1), 16)
632                pending_phy_rx_antennas = int(
633                        match_avail_antennas.group(2), 16)
634                continue
635
636            match_device_support = re.match('\s*Device supports (.*)\.', line)
637            if match_device_support and pending_phy_name:
638                pending_phy_features.append(match_device_support.group(1))
639                continue
640
641            if not all([current_band, pending_phy_name,
642                        line.startswith('\t')]):
643                continue
644
645            # E.g.
646            # * 2412 MHz [1] (20.0 dBm)
647            # * 2467 MHz [12] (20.0 dBm) (passive scan)
648            # * 2472 MHz [13] (disabled)
649            # * 5260 MHz [52] (19.0 dBm) (no IR, radar detection)
650            match_chan_info = re.search(
651                r'(?P<frequency>\d+) MHz'
652                r' (?P<chan_num>\[\d+\])'
653                r'(?: \((?P<tx_power_limit>[0-9.]+ dBm)\))?'
654                r'(?: \((?P<flags>[a-zA-Z, ]+)\))?', line)
655            if match_chan_info:
656                frequency = int(match_chan_info.group('frequency'))
657                current_band.frequencies.append(frequency)
658                flags_string = match_chan_info.group('flags')
659                if flags_string:
660                    current_band.frequency_flags[frequency] = frozenset(
661                        flags_string.split(','))
662                else:
663                    # Populate the dict with an empty set, to make
664                    # things uniform for client code.
665                    current_band.frequency_flags[frequency] = frozenset()
666                continue
667
668            # re_mcs needs to match something like:
669            # HT TX/RX MCS rate indexes supported: 0-15, 32
670            if re.search('HT TX/RX MCS rate indexes supported: ', line):
671                rate_string = line.split(':')[1].strip()
672                for piece in rate_string.split(','):
673                    if piece.find('-') > 0:
674                        # Must be a range like '  0-15'
675                        begin, end = piece.split('-')
676                        for index in range(int(begin), int(end) + 1):
677                            current_band.mcs_indices.append(index)
678                    else:
679                        # Must be a single rate like '32   '
680                        current_band.mcs_indices.append(int(piece))
681        if pending_phy_name:
682            add_pending_phy()
683        return all_phys
684
685
686    def remove_interface(self, interface, ignore_status=False):
687        """
688        Remove a WiFi interface from a PHY.
689
690        @param interface: string name of interface (e.g. mon0)
691        @param ignore_status: boolean True iff we should ignore failures
692                to remove the interface.
693
694        """
695        self._run('%s dev %s del' % (self._command_iw, interface),
696                  ignore_status=ignore_status)
697
698
699    def determine_security(self, supported_securities):
700        """Determines security from the given list of supported securities.
701
702        @param supported_securities: list of supported securities from scan
703
704        """
705        if not supported_securities:
706            security = SECURITY_OPEN
707        elif len(supported_securities) == 1:
708            security = supported_securities[0]
709        else:
710            security = SECURITY_MIXED
711        return security
712
713
714    def scan(self, interface, frequencies=(), ssids=()):
715        """Performs a scan.
716
717        @param interface: the interface to run the iw command against
718        @param frequencies: list of int frequencies in Mhz to scan.
719        @param ssids: list of string SSIDs to send probe requests for.
720
721        @returns a list of IwBss namedtuples; None if the scan fails
722
723        """
724        scan_result = self.timed_scan(interface, frequencies, ssids)
725        if scan_result is None:
726            return None
727        return scan_result.bss_list
728
729
730    def timed_scan(self, interface, frequencies=(), ssids=()):
731        """Performs a timed scan.
732
733        @param interface: the interface to run the iw command against
734        @param frequencies: list of int frequencies in Mhz to scan.
735        @param ssids: list of string SSIDs to send probe requests for.
736
737        @returns a IwTimedScan namedtuple; None if the scan fails
738
739        """
740        freq_param = ''
741        if frequencies:
742            freq_param = ' freq %s' % ' '.join(map(str, frequencies))
743        ssid_param = ''
744        if ssids:
745           ssid_param = ' ssid "%s"' % '" "'.join(ssids)
746
747        iw_command = '%s dev %s scan%s%s' % (self._command_iw,
748                interface, freq_param, ssid_param)
749        command = IW_TIME_COMMAND_FORMAT % iw_command
750        scan = self._run(command, ignore_status=True)
751        if scan.exit_status != 0:
752            # The device was busy
753            logging.debug('scan exit_status: %d', scan.exit_status)
754            return None
755        if not scan.stdout:
756            raise error.TestFail('Missing scan parse time')
757
758        if scan.stdout.startswith(IW_TIME_COMMAND_OUTPUT_START):
759            logging.debug('Empty scan result')
760            bss_list = []
761        else:
762            bss_list = self._parse_scan_results(scan.stdout)
763        scan_time = self._parse_scan_time(scan.stdout)
764        return IwTimedScan(scan_time, bss_list)
765
766
767    def scan_dump(self, interface):
768        """Dump the contents of the scan cache.
769
770        Note that this does not trigger a scan.  Instead, it returns
771        the kernel's idea of what BSS's are currently visible.
772
773        @param interface: the interface to run the iw command against
774
775        @returns a list of IwBss namedtuples; None if the scan fails
776
777        """
778        result = self._run('%s dev %s scan dump' % (self._command_iw,
779                                                    interface))
780        return self._parse_scan_results(result.stdout)
781
782
783    def set_tx_power(self, interface, power):
784        """
785        Set the transmission power for an interface.
786
787        @param interface: string name of interface to set Tx power on.
788        @param power: string power parameter. (e.g. 'auto').
789
790        """
791        self._run('%s dev %s set txpower %s' %
792                  (self._command_iw, interface, power))
793
794
795    def set_freq(self, interface, freq):
796        """
797        Set the frequency for an interface.
798
799        @param interface: string name of interface to set frequency on.
800        @param freq: int frequency
801
802        """
803        self._run('%s dev %s set freq %d' %
804                  (self._command_iw, interface, freq))
805
806
807    def set_regulatory_domain(self, domain_string):
808        """
809        Set the regulatory domain of the current machine.  Note that
810        the regulatory change happens asynchronously to the exit of
811        this function.
812
813        @param domain_string: string regulatory domain name (e.g. 'US').
814
815        """
816        self._run('%s reg set %s' % (self._command_iw, domain_string))
817
818
819    def get_regulatory_domain(self):
820        """
821        Get the regulatory domain of the current machine.
822
823        @returns a string containing the 2-letter regulatory domain name
824            (e.g. 'US').
825
826        """
827        output = self._run('%s reg get' % self._command_iw).stdout
828        m = re.search('^country (..):', output, re.MULTILINE)
829        if not m:
830            return None
831        return m.group(1)
832
833
834    def wait_for_scan_result(self, interface, bsses=(), ssids=(),
835                             timeout_seconds=30, wait_for_all=False):
836        """Returns a list of IWBSS objects for given list of bsses or ssids.
837
838        This method will scan for a given timeout and return all of the networks
839        that have a matching ssid or bss.  If wait_for_all is true and all
840        networks are not found within the given timeout an empty list will
841        be returned.
842
843        @param interface: which interface to run iw against
844        @param bsses: a list of BSS strings
845        @param ssids: a list of ssid strings
846        @param timeout_seconds: the amount of time to wait in seconds
847        @param wait_for_all: True to wait for all listed bsses or ssids; False
848                             to return if any of the networks were found
849
850        @returns a list of IwBss collections that contain the given bss or ssid;
851            if the scan is empty or returns an error code None is returned.
852
853        """
854
855        logging.info('Performing a scan with a max timeout of %d seconds.',
856                     timeout_seconds)
857
858        # If the in-progress scan takes more than 30 seconds to
859        # complete it will most likely never complete; abort.
860        # See crbug.com/309148
861        scan_results = list()
862        try:
863            scan_results = utils.poll_for_condition(
864                    condition=lambda: self.scan(interface),
865                    timeout=timeout_seconds,
866                    sleep_interval=5, # to allow in-progress scans to complete
867                    desc='Timed out getting IWBSSes that match desired')
868        except utils.TimeoutError as e:
869            pass
870
871        if not scan_results: # empty list or None
872            return None
873
874        # get all IWBSSes from the scan that match any of the desired
875        # ssids or bsses passed in
876        matching_iwbsses = filter(
877                lambda iwbss: iwbss.ssid in ssids or iwbss.bss in bsses,
878                scan_results)
879        if wait_for_all:
880            found_bsses = [iwbss.bss for iwbss in matching_iwbsses]
881            found_ssids = [iwbss.ssid for iwbss in matching_iwbsses]
882            # if an expected bss or ssid was not found, and it was required
883            # by the caller that all expected be found, return empty list
884            if any(bss not in found_bsses for bss in bsses) or any(
885                    ssid not in found_ssids for ssid in ssids):
886                return list()
887        return list(matching_iwbsses)
888
889
890    def wait_for_link(self, interface, timeout_seconds=10):
891        """Waits until a link completes on |interface|.
892
893        @param interface: which interface to run iw against.
894        @param timeout_seconds: the amount of time to wait in seconds.
895
896        @returns True if link was established before the timeout.
897
898        """
899        return utils.poll_for_condition(
900                # gets link results from running dev command, then assumes the
901                # link is completed if 'Not connected' is absent from stdout
902                condition=lambda: 'Not connected' not in self._run(
903                    '%s dev %s link' % (self._command_iw, interface)).stdout,
904                timeout=timeout_seconds,
905                sleep_interval=1,
906                desc='Wait until a link completes on |interface|')
907
908
909    def set_antenna_bitmap(self, phy, tx_bitmap, rx_bitmap):
910        """Set antenna chain mask on given phy (radio).
911
912        This function will set the antennas allowed to use for TX and
913        RX on the |phy| based on the |tx_bitmap| and |rx_bitmap|.
914        This command is only allowed when the interfaces on the phy are down.
915
916        @param phy: phy name
917        @param tx_bitmap: bitmap of allowed antennas to use for TX
918        @param rx_bitmap: bitmap of allowed antennas to use for RX
919
920        """
921        command = '%s phy %s set antenna %d %d' % (self._command_iw, phy,
922                                                   tx_bitmap, rx_bitmap)
923        self._run(command)
924
925
926    def get_event_logger(self):
927        """Create and return a IwEventLogger object.
928
929        @returns a IwEventLogger object.
930
931        """
932        local_file = IW_LOCAL_EVENT_LOG_FILE % (self._log_id)
933        self._log_id += 1
934        return iw_event_logger.IwEventLogger(self._host, self._command_iw,
935                                             local_file)
936
937
938    def vht_supported(self):
939        """Returns True if VHT is supported; False otherwise."""
940        result = self._run('%s list' % self._command_iw).stdout
941        if 'VHT Capabilities' in result:
942            return True
943        return False
944
945
946    def frequency_supported(self, frequency):
947        """Returns True if the given frequency is supported; False otherwise.
948
949        @param frequency: int Wifi frequency to check if it is supported by
950                          DUT.
951        """
952        phys = self.list_phys()
953        for phy in phys:
954            for band in phy.bands:
955                if frequency in band.frequencies:
956                    return True
957        return False
958
959
960    def get_fragmentation_threshold(self, phy):
961        """Returns the fragmentation threshold for |phy|.
962
963        @param phy: phy name
964        """
965        ret = self._run('%s phy %s info' % (self._command_iw, phy))
966        frag_regex = r'^\s+Fragmentation threshold:\s+([0-9]+)$'
967        match = re.search(frag_regex, ret.stdout, re.MULTILINE)
968
969        if match:
970            return int(match.group(1))
971
972        return None
973