1#!/usr/bin/env python3
2#
3#   Copyright 2021 - 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 time
18from acts import asserts
19from acts import signals
20from acts.test_decorators import test_tracker_info
21import acts_contrib.test_utils.wifi.wifi_test_utils as wutils
22from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
23from acts.controllers.ap_lib.hostapd_constants import BAND_2G
24from acts.controllers.ap_lib.hostapd_constants import BAND_5G
25from acts.controllers.ap_lib import hostapd_constants
26
27# TODO: Find a better way to get real country code and channels data.
28COUNTRY_5G_NOT_ALLOWED = ["JP", "GB", "DE"]
29WIFI_5G_NON_DFS_CHANNELS = [36, 38, 40, 42, 44, 46, 48, 149, 153, 157, 161, 165]
30WIFI_5G_DFS_CHANNELS = [52, 56, 60, 64, 100, 104, 108, 112, 116, 132, 136, 140]
31WIFI_EU_SRD_CHANNELS = [149, 153, 157, 161, 165]
32
33BRIDGED_AP_LAUNCH_INTERVAL_5_SECONDS = 5
34WifiEnums = wutils.WifiEnums
35
36
37class WifiCountrySoftApAcsTest(WifiBaseTest):
38    """WiFi WifiSoftApCountryAcsTest test class.
39
40    Test Bed Requirement:
41        * Android DUT x 1.
42        * OpenWrt x 1.
43    """
44
45    def setup_class(self):
46        super().setup_class()
47
48        self.dut = self.android_devices[0]
49        self.client = self.android_devices[1]
50
51        req_params = []
52        opt_param = ["cnss_diag_file", "pixel_models"]
53
54        self.unpack_userparams(
55            req_param_names=req_params, opt_param_names=opt_param)
56
57    def setup_test(self):
58        super().setup_test()
59        for ad in self.android_devices:
60            wutils.reset_wifi(ad)
61        wutils.wifi_toggle_state(self.dut, True)
62        wutils.wifi_toggle_state(self.client, True)
63
64    def teardown_test(self):
65        super().teardown_test()
66        if self.dut.droid.wifiIsApEnabled():
67            wutils.stop_wifi_tethering(self.dut)
68
69        for ad in self.android_devices:
70            wutils.reset_wifi(ad)
71            wutils.set_wifi_country_code(
72                ad, wutils.WifiEnums.CountryCode.US)
73
74    def teardown_class(self):
75        super().teardown_class()
76        for ad in self.android_devices:
77            wutils.reset_wifi(ad)
78
79        if "AccessPoint" in self.user_params:
80            del self.user_params["reference_networks"]
81            del self.user_params["open_network"]
82
83    def is_bridgedap_supported(self, *args):
84        return self.dut.droid.wifiIsBridgedApConcurrencySupported()
85
86    def set_country_code_and_verify(self, ad, country_code):
87        """ Set Country Code to DUT.
88
89        Args:
90            ad: An AndroidDevice object.
91            country_code: String; 2 letter ISO country code, e,g,. "US".
92        """
93        wutils.set_wifi_country_code(ad, country_code)
94        # Wi-Fi OFF and ON to make sure country code take effect.
95        wutils.wifi_toggle_state(ad, False)
96        wutils.wifi_toggle_state(ad, True)
97
98        country = ad.droid.wifiGetCountryCode()
99        asserts.assert_true(country == country_code,
100                            "country code {} is not set".format(country_code))
101        ad.log.info("Country code set to : {}".format(country))
102
103    def connect_wifi_network(self, init_sta_band, init_sta_chan):
104        """Enable OpenWrt with a 2G/5G channels and a DUT connect to it.
105
106        Args:
107            init_sta_band: String; "2g" or "5g".
108            init_sta_chan: Integer; use to setup OpenWrt 2G/5G channel.
109
110        Returns:
111            ap_freq: Integer'; represent the frequency of the AP which
112            the DUT connect to.
113        """
114
115        # Enable a Wi-Fi network and DUT connect to it.
116        if init_sta_band == BAND_2G:
117            connect = BAND_2G
118            channel_2g = init_sta_chan
119            channel_5g = hostapd_constants.AP_DEFAULT_CHANNEL_5G
120        elif init_sta_band == BAND_5G:
121            connect = BAND_5G
122            channel_2g = hostapd_constants.AP_DEFAULT_CHANNEL_2G
123            channel_5g = init_sta_chan
124
125        # Enable OpenWrt AP.
126        if "OpenWrtAP" in self.user_params:
127            self.openwrt = self.access_points[0]
128            self.configure_openwrt_ap_and_start(wpa_network=True,
129                                                channel_2g=channel_2g,
130                                                channel_5g=channel_5g)
131            self.ap1_2g = self.wpa_networks[0][BAND_2G]
132            self.ap1_5g = self.wpa_networks[0][BAND_5G]
133
134            self.openwrt.log.info("OpenWrt AP 2G: {}".format(self.ap1_2g))
135            self.openwrt.log.info("OpenWrt AP 5G: {}".format(self.ap1_5g))
136
137        if connect == BAND_2G:
138            wutils.connect_to_wifi_network(self.dut, self.ap1_2g)
139        elif connect == BAND_5G:
140            wutils.connect_to_wifi_network(self.dut, self.ap1_5g)
141
142        ap_freq = self.dut.droid.wifiGetConnectionInfo()["frequency"]
143        self.dut.log.info("DUT connected to AP on freq: {}, chan: {}".
144                          format(ap_freq, WifiEnums.freq_to_channel[ap_freq]))
145        return ap_freq
146
147    def enable_softap(self, ad):
148        """ Enable SoftAp of the DUT
149
150        Args:
151            ad: An AndroidDevice object.
152
153        Returns:
154            (freq1, freq2): Integer; a 2G frequency and a 5G frequency if DUT
155                            support BridgedAp.
156            freq: Integer; a frequency from SoftAp.
157            None, bandwidth: Just a placeholder, won't be used.
158
159        Raises:
160            TestFailure if no BridgedAp instances.
161        """
162        # Enable SoftAp
163        # Create SoftAp config.
164        config = wutils.create_softap_config()
165        # If DUT support BridgedAp, then two BridgedAp instances enabled.
166        if self.dut.droid.wifiIsBridgedApConcurrencySupported():
167            wutils.save_wifi_soft_ap_config(
168                ad,
169                config,
170                bands=[WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G,
171                       WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G_5G])
172        # If DUT does not support BridgedAp, 2G OR 5G SoftAp enabled.
173        else:
174            if self.init_softap_band == BAND_2G:
175                band = WifiEnums.WIFI_CONFIG_SOFTAP_BAND_2G
176            elif self.init_softap_band == BAND_5G:
177                band = WifiEnums.WIFI_CONFIG_SOFTAP_BAND_5G
178            wutils.save_wifi_soft_ap_config(ad, config, band=band)
179        wutils.start_wifi_tethering_saved_config(ad)
180        time.sleep(BRIDGED_AP_LAUNCH_INTERVAL_5_SECONDS)
181
182        # if DUT support BridgedAp:
183        if ad.droid.wifiIsBridgedApConcurrencySupported():
184            callbackId = ad.droid.registerSoftApCallback()
185            infos = wutils.get_current_softap_infos(ad, callbackId, True)
186            ad.droid.unregisterSoftApCallback(callbackId)
187            # if DUT BridgedAp has two instances, return two frequencies.
188            if len(infos) == 2:
189                freq_1 = infos[0]["frequency"]
190                freq_2 = infos[1]["frequency"]
191                return freq_1, freq_2
192            # if DUT BridgedAp has only one instances, return the frequency.
193            elif len(infos) == 1:
194                freq = infos[0]["frequency"]
195                return freq, None
196            else:
197                raise signals.TestFailure("There should be SoftAp instance.")
198        # if DUT does not support BridgedAp:
199        else:
200            # Return SoftAp frequency.
201            callbackId = ad.droid.registerSoftApCallback()
202            freq, bandwidth = wutils.get_current_softap_info(ad, callbackId,
203                                                             True)
204            ad.log.info("SoftAp freq: {}".format(freq))
205            ad.droid.unregisterSoftApCallback(callbackId)
206            return freq, bandwidth
207
208    def collect_acs_failures(self, freq1, freq2, country, init_sta_band,
209                             init_sta_chan, init_softap_band):
210        """ Verify SoftAp ACS rules and return error message when fail.
211
212        Args:
213            freq1: Integer; frequency from SoftAp.
214            freq2: Integer; frequency from SoftAp.
215            country: String; Two letters country code, e.g., "US".
216            init_sta_band: String; "2g" or "5g".
217            init_sta_chan: Integer; use to setup OpenWrt 2G/5G channel.
218            init_softap_band: String: "2g" or "5g".
219
220        Returns: List of string; contains failure messages.
221        """
222        # If DUT support BridgedAp(Dual SoftAp).
223        # Decide which is softap_2g_freq, which is softap_5g_freq
224        self.softap_freq_1 = freq1
225        if self.dut.droid.wifiIsBridgedApConcurrencySupported():
226            self.softap_freq_2 = freq2
227            if self.softap_freq_1 in WifiEnums.ALL_2G_FREQUENCIES:
228                self.softap_2g_freq = self.softap_freq_1
229            elif self.softap_freq_1 in WifiEnums.ALL_5G_FREQUENCIES:
230                self.softap_5g_freq = self.softap_freq_1
231            if self.softap_freq_2 in WifiEnums.ALL_2G_FREQUENCIES:
232                self.softap_2g_freq = self.softap_freq_2
233            elif self.softap_freq_2 in WifiEnums.ALL_5G_FREQUENCIES:
234                self.softap_5g_freq = self.softap_freq_2
235        # If DUT does not support BridgedAp(Dual SoftAp).
236        # Decide the frequency is softap_2g_freq or softap_5g_freq
237        else:
238            if self.softap_freq_1 in WifiEnums.ALL_2G_FREQUENCIES:
239                self.softap_2g_freq = self.softap_freq_1
240            elif self.softap_freq_1 in WifiEnums.ALL_5G_FREQUENCIES:
241                self.softap_5g_freq = self.softap_freq_1
242
243        # Verify ACS when SoftAp 2G enabled.
244        failures = []
245        if init_softap_band == BAND_2G:
246            if init_sta_band == BAND_2G:
247                self.dut.log.info("Verifying 2G SoftAp chan == 2G STA chan")
248                if self.softap_2g_freq != self.actual_sta_freq:
249                    failures.append("Expect 2G SoftAp chan == 2G STA chan")
250            else:
251                self.dut.log.info("Verifying SoftAp still operates on 2G")
252                if self.softap_2g_freq not in WifiEnums.ALL_2G_FREQUENCIES:
253                    failures.append("Expect SoftAp still operates on 2G")
254
255        # Verify ACS when SoftAp 5G enabled.
256        elif init_softap_band == BAND_5G:
257            if (country in COUNTRY_5G_NOT_ALLOWED or
258               init_sta_chan in WIFI_5G_DFS_CHANNELS or
259               init_sta_chan in WIFI_EU_SRD_CHANNELS):
260                self.dut.log.info("Verifying SoftAp fallback to 2G")
261                if self.softap_2g_freq not in WifiEnums.ALL_2G_FREQUENCIES:
262                    failures.append("Expect SoftAp fallback to 2G.")
263            else:
264                if init_sta_band == BAND_2G:
265                    self.dut.log.info("Verifying SoftAp still operates on 5G")
266                    if self.softap_5g_freq not in WifiEnums.ALL_5G_FREQUENCIES:
267                        failures.append("Expect SoftAp still operates on 5G.")
268                elif init_sta_chan in WIFI_5G_NON_DFS_CHANNELS:
269                    self.dut.log.info("Verify 5G SoftAp chan == 5g STA chan")
270                    if self.softap_5g_freq != self.actual_sta_freq:
271                        failures.append("Expect 5G SoftAp chan == 5G STA chan")
272        failures = "\n".join(failures)
273        return failures
274
275    def validate_country_softap_acs(self, country, init_sta_band,
276                                    init_sta_chan, init_softap_band):
277        """ Verify SoftAp ACS on certain country work as expected.
278
279        Steps:
280            Get country, STA band, STA channel from test case name.
281            Set a country code to the DUT.
282            Enable a Wi-Fi network.
283            DUT connects to the Wi-Fi  network.
284            DUT enable SoftAp.
285                P20 and previous
286                    Enable SoftAp (2G OR 5G).
287                P21 and later:
288                    Enable BridgedAp (2G AND 5G)
289            Get SoftAp(or BridgedAp) channel.
290            Get AP channel.
291            Verify Country SoftAp ACS.
292
293        Args:
294            country: String; Two letters country code, e.g., "US".
295            init_sta_band: String; "2g" or "5g".
296            init_sta_chan: Integer; use to setup OpenWrt 2G/5G channel.
297            init_softap_band: String: "2g" or "5g".
298
299        Returns: List of string; contains failure messages.
300         """
301        # Set a country code to the DUT.
302        self.set_country_code_and_verify(self.dut, country)
303        # Get DUT STA frequency.
304        self.actual_sta_freq = self.connect_wifi_network(init_sta_band,
305                                                         init_sta_chan)
306        # DUT Enable SoftAp.
307        freq1, freq2 = self.enable_softap(self.dut)
308        # Verify Country SoftAp ACS.
309        return self.collect_acs_failures(freq1, freq2, country, init_sta_band,
310                                         init_sta_chan, init_softap_band)
311
312    # Tests
313
314    @test_tracker_info(uuid="003c67f7-f4cc-4f04-ab34-28c71a7602d9")
315    def test_country_us_softap_acs_sta_2g_ch_1_softap_2g(self):
316        """Verify SoftAp ACS on STA 2G CH1 and SoftAp 2G in US.
317           Steps: See docstring of validate_country_softap_acs()."""
318        failures = self.validate_country_softap_acs("US", "2g", 1, "2g")
319        asserts.assert_false(failures, str(failures))
320
321    @test_tracker_info(uuid="b3c0a7a4-150f-469c-9191-8d446b2e2593")
322    def test_country_us_softap_acs_sta_5g_ch_36_softap_2g(self):
323        """Verify SoftAp ACS on STA 5G NON-DFS CH36 and SoftAp 2G in US.
324           Steps: See docstring of validate_country_softap_acs()."""
325        failures = self.validate_country_softap_acs("US", "5g", 36, "2g")
326        asserts.assert_false(failures, str(failures))
327
328    @test_tracker_info(uuid="7c660706-e63d-4753-bb6e-dacdf4c36cc0")
329    def test_country_us_softap_acs_sta_5g_ch_132_softap_2g(self):
330        """Verify SoftAp ACS on STA 5G DFS CH52 and SoftAp 2G in US.
331           Steps: See docstring of validate_country_softap_acs()."""
332        failures = self.validate_country_softap_acs("US", "5g", 132, "2g")
333        asserts.assert_false(failures, str(failures))
334
335    @test_tracker_info(uuid="31973348-852e-4cd7-9a72-6e8f333623c5")
336    def test_country_de_softap_acs_sta_5g_ch_161_softap_2g(self):
337        """Verify SoftAp ACS on STA 5G EU SRD CH149 and SoftAp 2G in DE.
338           Steps: See docstring of validate_country_softap_acs()."""
339        failures = self.validate_country_softap_acs("US", "5g", 161, "2g")
340        asserts.assert_false(failures, str(failures))
341
342    @test_tracker_info(uuid="8ebba60c-a32c-46b3-b9da-411b1ef66288")
343    def test_country_us_softap_acs_sta_2g_ch_1_softap_5g(self):
344        """Verify SoftAp ACS on STA 2G CH1 and SoftAp 5G in US.
345           Steps: See docstring of validate_country_softap_acs()."""
346        failures = self.validate_country_softap_acs("US", "2g", 1, "5g")
347        asserts.assert_false(failures, str(failures))
348
349    @test_tracker_info(uuid="503ece09-3030-4a69-ae15-320f5104ddd2")
350    def test_country_us_softap_acs_sta_5g_ch_36_softap_5g(self):
351        """Verify SoftAp ACS on STA 5G NON-DFS CH36 and SoftAp 5G in US.
352           Steps: See docstring of validate_country_softap_acs()."""
353        failures = self.validate_country_softap_acs("US", "5g", 36, "5g")
354        asserts.assert_false(failures, str(failures))
355
356    @test_tracker_info(uuid="35a5f2f5-067d-4d67-aeb8-58fb253f4b97")
357    def test_country_us_softap_acs_sta_5g_ch_132_softap_5g(self):
358        """Verify SoftAp ACS on STA 5G DFS CH52 and SoftAp 5G in US.
359           Steps: See docstring of validate_country_softap_acs()."""
360        failures = self.validate_country_softap_acs("US", "5g", 132, "5g")
361        asserts.assert_false(failures, str(failures))
362
363    @test_tracker_info(uuid="866954a3-72b6-4e7d-853f-9e1659cdf305")
364    def test_country_de_softap_acs_sta_5g_ch_161_softap_5g(self):
365        """Verify SoftAp ACS on STA 5G EU SRD CH149 and SoftAp 5G in DE.
366           Steps: See docstring of validate_country_softap_acs()."""
367        failures = self.validate_country_softap_acs("DE", "5g", 161, "5g")
368        asserts.assert_false(failures, str(failures))
369
370    @test_tracker_info(uuid="866954a3-72b6-4e7d-853f-9e1659cdf305")
371    def test_country_jp_softap_acs_sta_5g_ch_36_softap_5g(self):
372        """Verify SoftAp ACS on STA 5G EU SRD CH149 and SoftAp 5G in DE.
373           Steps: See docstring of validate_country_softap_acs()."""
374        failures = self.validate_country_softap_acs("JP", "5g", 36, "5g")
375        asserts.assert_false(failures, str(failures))
376