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