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