• 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
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