1#!/usr/bin/python3.4
2#
3#   Copyright 2017 - The Android Open Source Project
4#
5#   Licensed under the Apache License, Version 2.0 (the "License");
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of the License at
8#
9#       http://www.apache.org/licenses/LICENSE-2.0
10#
11#   Unless required by applicable law or agreed to in writing, software
12#   distributed under the License is distributed on an "AS IS" BASIS,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16
17import queue
18import time
19
20from acts import asserts
21from acts.test_decorators import test_tracker_info
22from acts.test_utils.tel.tel_test_utils import WIFI_CONFIG_APBAND_5G
23from acts.test_utils.wifi import wifi_test_utils as wutils
24from acts.test_utils.wifi.rtt import rtt_const as rconsts
25from acts.test_utils.wifi.rtt import rtt_test_utils as rutils
26from acts.test_utils.wifi.rtt.RttBaseTest import RttBaseTest
27
28
29class RangeApSupporting11McTest(RttBaseTest):
30    """Test class for RTT ranging to Access Points which support IEEE 802.11mc"""
31
32    # Number of RTT iterations
33    NUM_ITER = 10
34
35    # Time gap (in seconds) between iterations
36    TIME_BETWEEN_ITERATIONS = 0
37
38    # Soft AP SSID
39    SOFT_AP_SSID = "RTT_TEST_SSID"
40
41    # Soft AP Password (irrelevant)
42    SOFT_AP_PASSWORD = "ABCDEFGH"
43
44    # Time to wait before configuration changes
45    WAIT_FOR_CONFIG_CHANGES_SEC = 1
46
47    def run_test_rtt_80211mc_supporting_aps(self, dut, accuracy_evaluation=False):
48        """Scan for APs and perform RTT only to those which support 802.11mc
49        Args:
50            dut: test device
51            accuracy_evaluation: False - only evaluate success rate.
52                                 True - evaluate both success rate and accuracy
53                                 default is False.
54        """
55        rtt_supporting_aps = rutils.select_best_scan_results(
56            rutils.scan_with_rtt_support_constraint(dut, True, repeat=10),
57            select_count=2)
58        dut.log.debug("RTT Supporting APs=%s", rtt_supporting_aps)
59        events = rutils.run_ranging(dut, rtt_supporting_aps, self.NUM_ITER,
60                                    self.TIME_BETWEEN_ITERATIONS)
61        stats = rutils.analyze_results(events, self.rtt_reference_distance_mm,
62                                       self.rtt_reference_distance_margin_mm,
63                                       self.rtt_min_expected_rssi_dbm,
64                                       self.lci_reference, self.lcr_reference)
65        dut.log.debug("Stats=%s", stats)
66
67        for bssid, stat in stats.items():
68            asserts.assert_true(
69                stat['num_no_results'] == 0,
70                "Missing (timed-out) results",
71                extras=stats)
72            asserts.assert_false(
73                stat['any_lci_mismatch'], "LCI mismatch", extras=stats)
74            asserts.assert_false(
75                stat['any_lcr_mismatch'], "LCR mismatch", extras=stats)
76            asserts.assert_false(
77                stat['invalid_num_attempted'],
78                "Invalid (0) number of attempts",
79                extras=stats)
80            asserts.assert_false(
81                stat['invalid_num_successful'],
82                "Invalid (0) number of successes",
83                extras=stats)
84            asserts.assert_equal(
85                stat['num_invalid_rssi'], 0, "Invalid RSSI", extras=stats)
86            asserts.assert_true(
87                stat['num_failures'] <=
88                self.rtt_max_failure_rate_two_sided_rtt_percentage *
89                stat['num_results'] / 100,
90                "Failure rate is too high",
91                extras=stats)
92            if accuracy_evaluation:
93                asserts.assert_true(
94                    stat['num_range_out_of_margin'] <=
95                    self.rtt_max_margin_exceeded_rate_two_sided_rtt_percentage *
96                    stat['num_success_results'] / 100,
97                    "Results exceeding error margin rate is too high",
98                    extras=stats)
99        asserts.explicit_pass("RTT test done", extras=stats)
100
101    @test_tracker_info(uuid="6705270f-924b-4bef-b50a-0f0a7eb9ce52")
102    def test_rtt_80211mc_supporting_aps(self):
103        """Scan for APs and perform RTT only to those which support 802.11mc,
104        Functionality test: Only evaluate success rate."""
105        dut = self.android_devices[0]
106        self.run_test_rtt_80211mc_supporting_aps(dut)
107
108    @test_tracker_info(uuid="56a8ca4c-b69d-436e-aa80-e86adb6f57d8")
109    def test_rtt_80211mc_supporting_aps_with_accuracy_evaluation(self):
110        """Scan for APs and perform RTT only to those which support 802.11mc,
111        Performance test: evaluate success rate and accuracy."""
112        dut = self.android_devices[0]
113        self.run_test_rtt_80211mc_supporting_aps(dut, accuracy_evaluation=True)
114
115    @test_tracker_info(uuid="eb3fc9f5-ae15-47f5-8468-697bb9aa9ddf")
116    def test_rtt_in_and_after_softap_mode(self):
117        """Verify behavior when a SoftAP is enabled and then disabled on the
118        device:
119
120        - SAP Enabled: depending on device characteristics RTT may succeed or
121                       fail.
122        - SAP Disabled: RTT must now succeed.
123        """
124        supp_required_params = ("dbs_supported_models", )
125        self.unpack_userparams(supp_required_params)
126
127        dut = self.android_devices[0]
128
129        rtt_supporting_aps = rutils.select_best_scan_results(
130            rutils.scan_with_rtt_support_constraint(dut, True, repeat=10),
131            select_count=1)
132        dut.log.debug("RTT Supporting APs=%s", rtt_supporting_aps)
133
134        # phase 1 (pre-SAP)
135        events = rutils.run_ranging(dut, rtt_supporting_aps, self.NUM_ITER,
136                                    self.TIME_BETWEEN_ITERATIONS)
137        stats = rutils.analyze_results(events, self.rtt_reference_distance_mm,
138                                       self.rtt_reference_distance_margin_mm,
139                                       self.rtt_min_expected_rssi_dbm,
140                                       self.lci_reference, self.lcr_reference)
141        dut.log.debug("Stats Phase 1 (pre-SAP)=%s", stats)
142
143        for bssid, stat in stats.items():
144            asserts.assert_true(
145                stat['num_no_results'] == 0,
146                "Phase 1 (pre-SAP) missing (timed-out) results",
147                extras=stats)
148
149        # phase 2 (SAP)
150        wutils.start_wifi_tethering(
151            dut,
152            self.SOFT_AP_SSID,
153            self.SOFT_AP_PASSWORD,
154            band=WIFI_CONFIG_APBAND_5G,
155            hidden=False)
156        time.sleep(self.WAIT_FOR_CONFIG_CHANGES_SEC)
157
158        if dut.model not in self.dbs_supported_models:
159            rutils.wait_for_event(dut, rconsts.BROADCAST_WIFI_RTT_NOT_AVAILABLE)
160            asserts.assert_false(dut.droid.wifiIsRttAvailable(),
161                                 "RTT is available")
162
163        events = rutils.run_ranging(dut, rtt_supporting_aps, self.NUM_ITER,
164                                    self.TIME_BETWEEN_ITERATIONS)
165        stats = rutils.analyze_results(events, self.rtt_reference_distance_mm,
166                                       self.rtt_reference_distance_margin_mm,
167                                       self.rtt_min_expected_rssi_dbm,
168                                       self.lci_reference, self.lcr_reference)
169        dut.log.debug("Stats Phase 2 (SAP)=%s", stats)
170
171        for bssid, stat in stats.items():
172            if dut.model in self.dbs_supported_models:
173                asserts.assert_true(
174                    stat['num_no_results'] == 0,
175                    "Phase 2 (SAP) missing (timed-out) results",
176                    extras=stats)
177            else:
178                asserts.assert_true(
179                    stat['num_success_results'] == 0,
180                    "Phase 2 (SAP) valid results - but unexpected in SAP!?",
181                    extras=stats)
182
183        # phase 3 (post-SAP)
184
185        # enabling Wi-Fi first: on some devices this will also disable SAP
186        # (that's the scenario we're primarily testing). Additionally,
187        # explicitly disable SAP (which may be a NOP on some devices).
188        wutils.wifi_toggle_state(dut, True)
189        time.sleep(self.WAIT_FOR_CONFIG_CHANGES_SEC)
190        wutils.stop_wifi_tethering(dut)
191
192        if dut.model not in self.dbs_supported_models:
193            rutils.wait_for_event(dut, rconsts.BROADCAST_WIFI_RTT_AVAILABLE)
194            asserts.assert_true(dut.droid.wifiIsRttAvailable(),
195                                "RTT is not available")
196
197        events = rutils.run_ranging(dut, rtt_supporting_aps, self.NUM_ITER,
198                                    self.TIME_BETWEEN_ITERATIONS)
199        stats = rutils.analyze_results(events, self.rtt_reference_distance_mm,
200                                       self.rtt_reference_distance_margin_mm,
201                                       self.rtt_min_expected_rssi_dbm,
202                                       self.lci_reference, self.lcr_reference)
203        dut.log.debug("Stats Phase 3 (post-SAP)=%s", stats)
204
205        for bssid, stat in stats.items():
206            asserts.assert_true(
207                stat['num_no_results'] == 0,
208                "Phase 3 (post-SAP) missing (timed-out) results",
209                extras=stats)
210
211    #########################################################################
212    #
213    # LEGACY API test code
214    #
215    #########################################################################
216
217    @test_tracker_info(uuid="18be9737-2f03-4e35-9a23-f722dea7b82d")
218    def test_legacy_rtt_80211mc_supporting_aps(self):
219        """Scan for APs and perform RTT only to those which support 802.11mc - using
220        the LEGACY API!
221        """
222        dut = self.android_devices[0]
223        rtt_supporting_aps = rutils.select_best_scan_results(
224            rutils.scan_with_rtt_support_constraint(dut, True, repeat=10),
225            select_count=2)
226        dut.log.debug("RTT Supporting APs=%s", rtt_supporting_aps)
227
228        rtt_configs = []
229        for ap in rtt_supporting_aps:
230            rtt_configs.append(self.rtt_config_from_scan_result(ap))
231        dut.log.debug("RTT configs=%s", rtt_configs)
232
233        results = []
234        num_missing = 0
235        num_failed_aborted = 0
236        for i in range(self.NUM_ITER):
237            idx = dut.droid.wifiRttStartRanging(rtt_configs)
238            event = None
239            try:
240                events = dut.ed.pop_events("WifiRttRanging%d" % idx, 30)
241                dut.log.debug("Event=%s", events)
242                for event in events:
243                    if rconsts.EVENT_CB_RANGING_KEY_RESULTS in event["data"]:
244                        results.append(event["data"][
245                            rconsts.EVENT_CB_RANGING_KEY_RESULTS])
246                    else:
247                        self.log.info("RTT failed/aborted - %s", event)
248                        results.append([])
249                        num_failed_aborted = num_failed_aborted + 1
250            except queue.Empty:
251                self.log.debug("Waiting for RTT event timed out.")
252                results.append([])
253                num_missing = num_missing + 1
254
255        # basic error checking:
256        # 1. no missing
257        # 2. no full failed/aborted (i.e. operation not even tried)
258        # 3. overall (all BSSIDs) success rate > threshold
259        asserts.assert_equal(
260            num_missing,
261            0,
262            "Missing results (timeout waiting for event)",
263            extras={"data": results})
264        asserts.assert_equal(
265            num_failed_aborted,
266            0,
267            "Failed or aborted operations (not tried)",
268            extras={"data": results})
269
270        num_results = 0
271        num_errors = 0
272        for result_group in results:
273            num_results = num_results + len(result_group)
274            for result in result_group:
275                if result["status"] != 0:
276                    num_errors = num_errors + 1
277
278        extras = [
279            results, {
280                "num_results": num_results,
281                "num_errors": num_errors
282            }
283        ]
284        asserts.assert_true(
285            num_errors <= self.rtt_max_failure_rate_two_sided_rtt_percentage *
286            num_results / 100,
287            "Failure rate is too high",
288            extras={"data": extras})
289        asserts.explicit_pass("RTT test done", extras={"data": extras})
290
291    def rtt_config_from_scan_result(self, scan_result):
292        """Creates an Rtt configuration based on the scan result of a network.
293        """
294        WifiEnums = wutils.WifiEnums
295        ScanResult = WifiEnums.ScanResult
296        RttParam = WifiEnums.RttParam
297        RttBW = WifiEnums.RttBW
298        RttPreamble = WifiEnums.RttPreamble
299        RttType = WifiEnums.RttType
300
301        scan_result_channel_width_to_rtt = {
302            ScanResult.CHANNEL_WIDTH_20MHZ: RttBW.BW_20_SUPPORT,
303            ScanResult.CHANNEL_WIDTH_40MHZ: RttBW.BW_40_SUPPORT,
304            ScanResult.CHANNEL_WIDTH_80MHZ: RttBW.BW_80_SUPPORT,
305            ScanResult.CHANNEL_WIDTH_160MHZ: RttBW.BW_160_SUPPORT,
306            ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ: RttBW.BW_160_SUPPORT
307        }
308        p = {}
309        freq = scan_result[RttParam.frequency]
310        p[RttParam.frequency] = freq
311        p[RttParam.BSSID] = scan_result[WifiEnums.BSSID_KEY]
312        if freq > 5000:
313            p[RttParam.preamble] = RttPreamble.PREAMBLE_VHT
314        else:
315            p[RttParam.preamble] = RttPreamble.PREAMBLE_HT
316        cf0 = scan_result[RttParam.center_freq0]
317        if cf0 > 0:
318            p[RttParam.center_freq0] = cf0
319        cf1 = scan_result[RttParam.center_freq1]
320        if cf1 > 0:
321            p[RttParam.center_freq1] = cf1
322        cw = scan_result["channelWidth"]
323        p[RttParam.channel_width] = cw
324        p[RttParam.bandwidth] = scan_result_channel_width_to_rtt[cw]
325        if scan_result["is80211McRTTResponder"]:
326            p[RttParam.request_type] = RttType.TYPE_TWO_SIDED
327        else:
328            p[RttParam.request_type] = RttType.TYPE_ONE_SIDED
329        return p
330