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