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