• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2014 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
5from __future__ import division
6
7import logging
8import random
9import string
10import time
11
12from autotest_lib.server.cros.network import frame_sender
13from autotest_lib.server.cros.network import hostap_config
14from autotest_lib.server import site_linux_system
15from autotest_lib.client.common_lib import error
16from autotest_lib.client.common_lib import utils
17from autotest_lib.client.common_lib.cros.network import tcpdump_analyzer
18from autotest_lib.server.cros.network import wifi_cell_test_base
19
20
21class network_WiFi_ChannelScanDwellTime(wifi_cell_test_base.WiFiCellTestBase):
22    """Test for determine channel scan dwell time."""
23    version = 1
24
25    KNOWN_TEST_PREFIX = 'network_WiFi'
26    SUFFIX_LETTERS = string.ascii_lowercase + string.digits
27    DELAY_INTERVAL_MILLISECONDS = 1
28    SCAN_RETRY_TIMEOUT_SECONDS = 10
29    NUM_BSS = 1024
30    MISSING_BEACON_THRESHOLD = 2
31    MAX_DWELL_TIME_MS = 250
32    MIN_DWELL_TIME_MS = 5
33    FREQUENCY_MHZ = 2412
34    MSEC_PER_SEC = 1000
35    SCAN_START_DELAY_MS = 200
36
37    def _build_ssid_prefix(self):
38        """Build ssid prefix."""
39        unique_salt = ''.join([random.choice(self.SUFFIX_LETTERS)
40                               for _ in range(5)])
41        prefix = self.__class__.__name__[len(self.KNOWN_TEST_PREFIX):]
42        prefix = prefix.lstrip('_')
43        prefix += '_' + unique_salt + '_'
44        return prefix[-23:]
45
46
47    def _get_ssid_index(self, ssid):
48        """Return the SSID index from an SSID string.
49
50        Given an SSID of the form [testName]_[salt]_[index], returns |index|.
51
52        @param ssid: full SSID, as received in scan results.
53        @return int SSID index.
54        """
55        return int(ssid.split('_')[-1], 16)
56
57
58    def _get_beacon_timestamp(self, beacon_frames, ssid_num):
59        """Return the time at which the beacon with |ssid_num| was transmitted.
60
61        If multiple beacons match |ssid_num|, return the time of the first
62        matching beacon.
63
64        @param beacon_frames: List of Frames.
65        @param ssid_num: int SSID number to match.
66        @return datetime time at which beacon was transmitted.
67        """
68        for frame in beacon_frames:
69            if self._get_ssid_index(frame.ssid) == ssid_num:
70                return frame.time_datetime
71        else:
72            raise error.TestFail('Failed to find SSID %d in pcap.' % ssid_num)
73
74
75    def _get_dwell_time(self, bss_list, sent_beacon_frames):
76        """Parse scan result to get dwell time.
77
78        Calculate dwell time based on the SSIDs in the scan result.
79
80        @param bss_list: List of BSSs.
81        @param sent_beacon_frames: List of Frames, as captured on sender.
82
83        @return int dwell time in ms.
84        """
85        ssid_index = [self._get_ssid_index(bss) for bss in bss_list]
86        # Calculate dwell time based on the start ssid index and end ssid index.
87        ssid_index.sort()
88        index_diff = ssid_index[-1] - ssid_index[0]
89
90        # Check if number of missed beacon frames exceed the test threshold.
91        missed_beacons = index_diff - (len(ssid_index) - 1)
92        if missed_beacons > self.MISSING_BEACON_THRESHOLD:
93            logging.info('Missed %d beacon frames, SSID Index: %r',
94                         missed_beacons, ssid_index)
95            raise error.TestFail('DUT missed more than %d beacon frames' %
96                                 missed_beacons)
97
98        first_ssid_tstamp = self._get_beacon_timestamp(
99            sent_beacon_frames, ssid_index[0])
100        last_ssid_tstamp = self._get_beacon_timestamp(
101            sent_beacon_frames, ssid_index[-1])
102        return int(round(
103            (last_ssid_tstamp - first_ssid_tstamp).total_seconds() *
104            self.MSEC_PER_SEC))
105
106    def _scan_frequencies(self, frequencies):
107        """Scan for BSSs on the provided frequencies.
108
109        The result of the scan is stored in self._bss_list.
110
111        @return True if scan was successfully triggered, even
112                if no BSS was found. False otherwise.
113        """
114        self._bss_list = self.context.client.iw_runner.scan(
115                self.context.client.wifi_if,
116                frequencies=frequencies)
117
118        return self._bss_list is not None
119
120    def _channel_dwell_time_test(self, single_channel):
121        """Perform test to determine channel dwell time.
122
123        This function invoke FrameSender to continuously send beacon frames
124        for specific number of BSSs with specific delay, the SSIDs of the
125        BSS are in hex numerical order. And at the same time, perform wifi scan
126        on the DUT. The index in the SSIDs of the scan result will be used to
127        interpret the relative start time and end time of the channel scan.
128
129        @param single_channel: bool perform single channel scan if true.
130
131        @return int dwell time in ms.
132
133        """
134        channel = hostap_config.HostapConfig.get_channel_for_frequency(
135            self.FREQUENCY_MHZ)
136        # Configure an AP to inject beacons.
137        self.context.configure(hostap_config.HostapConfig(channel=channel))
138        self.context.capture_host.start_capture(self.FREQUENCY_MHZ)
139        ssid_prefix = self._build_ssid_prefix()
140
141        with frame_sender.FrameSender(self.context.router, 'beacon', channel,
142                                      ssid_prefix=ssid_prefix,
143                                      num_bss=self.NUM_BSS,
144                                      frame_count=0,
145                                      delay=self.DELAY_INTERVAL_MILLISECONDS):
146            if single_channel:
147                frequencies = [self.FREQUENCY_MHZ]
148            else:
149                frequencies = []
150            # Don't immediately start the scan, wait a bit so the AP has enough
151            # time to start actually sending beacon frames before the scan
152            # starts.
153            time.sleep(self.SCAN_START_DELAY_MS / self.MSEC_PER_SEC)
154            # Perform scan
155            try:
156                utils.poll_for_condition(
157                        condition=lambda: self._scan_frequencies(frequencies),
158                        timeout=self.SCAN_RETRY_TIMEOUT_SECONDS,
159                        sleep_interval=0.5)
160            except utils.TimeoutError:
161                raise error.TestFail('Unable to trigger scan on client.')
162            if not self._bss_list:
163                raise error.TestFail('Failed to find any BSS')
164
165            # Remaining work is done outside the FrameSender
166            # context. This is to ensure that no additional frames are
167            # transmitted while we're waiting for the packet capture
168            # to complete.
169        pcap_path = self.context.capture_host.stop_capture()[0].local_pcap_path
170
171        # Filter scan result based on ssid prefix to remove any cached
172        # BSSs from previous run.
173        result_list = [bss.ssid for bss in self._bss_list if
174                       bss.ssid and bss.ssid.startswith(ssid_prefix)]
175        if not result_list:
176            raise error.TestFail('Failed to find any BSS for this test')
177
178        beacon_frames = tcpdump_analyzer.get_frames(
179            pcap_path, tcpdump_analyzer.WLAN_BEACON_ACCEPTOR,
180            reject_bad_fcs=False)
181        # Filter beacon frames based on ssid prefix.
182        result_beacon_frames = [frame for frame in beacon_frames if frame.ssid
183                                and frame.ssid.startswith(ssid_prefix)]
184        if not result_beacon_frames:
185            raise error.TestFail('Failed to find any beacons for this test')
186        return self._get_dwell_time(result_list, result_beacon_frames)
187
188
189    def run_once(self):
190        """Measure channel dwell time for single-channel scan"""
191        self.context.router.require_capabilities(
192            [site_linux_system.LinuxSystem.CAPABILITY_SEND_MANAGEMENT_FRAME])
193        # Claim the control over the wifi interface from WiFiClient, which
194        # will prevent shill and wpa_supplicant from managing that interface.
195        # So this test can have the sole ownership of the interface and can
196        # perform scans without interference from shill and wpa_supplicant.
197        self.context.client.claim_wifi_if()
198        try:
199            # Get channel dwell time for single-channel scan
200            dwell_time = self._channel_dwell_time_test(True)
201            # Ensure that the measured value is sane, so a glitch doesn't
202            # pollute the perf dataset.
203            if (dwell_time < self.MIN_DWELL_TIME_MS or
204                    dwell_time > self.MAX_DWELL_TIME_MS):
205                raise error.TestFail(
206                        'Dwell time %d ms is not within range [%dms,%dms]' %
207                        (dwell_time, self.MIN_DWELL_TIME_MS,
208                            self.MAX_DWELL_TIME_MS))
209            logging.info('Channel dwell time for single-channel scan: %d ms',
210                         dwell_time)
211            self.output_perf_value(
212                    'dwell_time_single_channel_scan', dwell_time, units='ms',
213                    higher_is_better=False)
214        finally:
215            self.context.client.release_wifi_if()
216