1#!/usr/bin/env python3.4
2#
3#   Copyright 2018 - 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 itertools
18import pprint
19import queue
20import time
21
22import acts.base_test
23import acts.signals as signals
24import acts.test_utils.wifi.wifi_test_utils as wutils
25import acts.utils
26
27from acts import asserts
28from acts.controllers.android_device import SL4A_APK_NAME
29from acts.test_decorators import test_tracker_info
30from acts.test_utils.wifi.WifiBaseTest import WifiBaseTest
31from acts.test_utils.wifi import wifi_constants
32
33WifiEnums = wutils.WifiEnums
34
35# Network request timeout to use.
36NETWORK_REQUEST_TIMEOUT_MS = 60 * 1000
37# Timeout to wait for instant failure.
38NETWORK_REQUEST_INSTANT_FAILURE_TIMEOUT_SEC = 5
39
40class WifiNetworkRequestTest(WifiBaseTest):
41    """Tests for NetworkRequest with WifiNetworkSpecifier API surface.
42
43    Test Bed Requirement:
44    * one Android device
45    * Several Wi-Fi networks visible to the device, including an open Wi-Fi
46      network.
47    """
48
49    def setup_class(self):
50        super().setup_class()
51
52        self.dut = self.android_devices[0]
53        wutils.wifi_test_device_init(self.dut)
54        req_params = []
55        opt_param = [
56            "open_network", "reference_networks"
57        ]
58        self.unpack_userparams(
59            req_param_names=req_params, opt_param_names=opt_param)
60
61        if "AccessPoint" in self.user_params:
62            self.legacy_configure_ap_and_start(wpa_network=True,
63                                               wep_network=True)
64
65        asserts.assert_true(
66            len(self.reference_networks) > 0,
67            "Need at least one reference network with psk.")
68        self.wpa_psk_2g = self.reference_networks[0]["2g"]
69        self.wpa_psk_5g = self.reference_networks[0]["5g"]
70        self.open_2g = self.open_network[0]["2g"]
71        self.open_5g = self.open_network[0]["5g"]
72
73    def setup_test(self):
74        self.dut.droid.wakeLockAcquireBright()
75        self.dut.droid.wakeUpNow()
76        self.remove_approvals()
77        self.clear_deleted_ephemeral_networks()
78        wutils.wifi_toggle_state(self.dut, True)
79        self.dut.ed.clear_all_events()
80
81    def teardown_test(self):
82        self.dut.droid.wakeLockRelease()
83        self.dut.droid.goToSleepNow()
84        self.dut.droid.wifiReleaseNetworkAll()
85        self.dut.droid.wifiDisconnect()
86        wutils.reset_wifi(self.dut)
87        # Ensure we disconnected from the current network before the next test.
88        if self.dut.droid.wifiGetConnectionInfo()["supplicant_state"] != "disconnected":
89            wutils.wait_for_disconnect(self.dut)
90        wutils.wifi_toggle_state(self.dut, False)
91        self.dut.ed.clear_all_events()
92
93    def on_fail(self, test_name, begin_time):
94        self.dut.take_bug_report(test_name, begin_time)
95        self.dut.cat_adb_log(test_name, begin_time)
96
97    def teardown_class(self):
98        if "AccessPoint" in self.user_params:
99            del self.user_params["reference_networks"]
100            del self.user_params["open_network"]
101
102    """Helper Functions"""
103    def wait_for_network_lost(self):
104        """
105        Wait for network lost callback from connectivity service (wifi
106        disconnect).
107
108        Args:
109            ad: Android device object.
110        """
111        try:
112            self.dut.droid.wifiStartTrackingStateChange()
113            event = self.dut.ed.pop_event(
114                wifi_constants.WIFI_NETWORK_CB_ON_LOST, 10)
115            self.dut.droid.wifiStopTrackingStateChange()
116        except queue.Empty:
117            raise signals.TestFailure(
118                "Device did not disconnect from the network")
119
120    def remove_approvals(self):
121        self.dut.log.debug("Removing all approvals from sl4a app")
122        self.dut.adb.shell(
123            "cmd wifi network-requests-remove-user-approved-access-points"
124            + " " + SL4A_APK_NAME)
125
126    def clear_deleted_ephemeral_networks(self):
127        self.dut.log.debug("Clearing deleted ephemeral networks")
128        self.dut.adb.shell(
129            "cmd wifi clear-deleted-ephemeral-networks")
130
131    @test_tracker_info(uuid="d70c8380-61ba-48a3-b76c-a0b55ce4eabf")
132    def test_connect_to_wpa_psk_2g_with_ssid(self):
133        """
134        Initiates a connection to network via network request with specific SSID
135
136        Steps:
137        1. Send a network specifier with the specific SSID/credentials of
138           WPA-PSK 2G network.
139        2. Wait for platform to scan and find matching networks.
140        3. Simulate user selecting the network.
141        4. Ensure that the device connects to the network.
142        """
143        wutils.wifi_connect_using_network_request(self.dut, self.wpa_psk_2g,
144                                                  self.wpa_psk_2g)
145
146    @test_tracker_info(uuid="44f2bf40-a282-4413-b8f2-3abb3caa49ee")
147    def test_connect_to_open_5g_with_ssid(self):
148        """
149        Initiates a connection to network via network request with specific SSID
150
151        Steps:
152        1. Send a network specifier with the specific SSID of Open 5G network.
153        2. Wait for platform to scan and find matching networks.
154        3. Simulate user selecting the network.
155        4. Ensure that the device connects to the network.
156        """
157        wutils.wifi_connect_using_network_request(self.dut, self.open_5g,
158                                                  self.open_5g)
159
160    @test_tracker_info(uuid="09d1823e-4f85-42f8-8c20-de7637f6d4be")
161    def test_connect_to_wpa_psk_5g_with_ssid_pattern(self):
162        """
163        Initiates a connection to network via network request with SSID pattern
164
165        Steps:
166        1. Send a network specifier with the SSID pattern/credentials of
167           WPA-PSK 5G network.
168        2. Wait for platform to scan and find matching networks.
169        3. Simulate user selecting the network.
170        4. Ensure that the device connects to the network.
171        """
172        network_specifier = self.wpa_psk_5g.copy();
173        # Remove ssid & replace with ssid pattern.
174        network_ssid = network_specifier.pop(WifiEnums.SSID_KEY)
175        # Remove the last element of ssid & replace with .* to create a matching
176        # pattern.
177        network_specifier[WifiEnums.SSID_PATTERN_KEY] = network_ssid[:-1] + ".*"
178        wutils.wifi_connect_using_network_request(self.dut, self.wpa_psk_5g,
179                                                  network_specifier)
180
181    @test_tracker_info(uuid="52216329-06f1-45ef-8d5f-de8b02d9f975")
182    def test_connect_to_open_5g_after_connecting_to_wpa_psk_2g(self):
183        """
184        Initiates a connection to network via network request with SSID pattern
185
186        Steps:
187        1. Send a network specifier with the specific SSID of Open 5G network.
188        2. Wait for platform to scan and find matching networks.
189        3. Simulate user selecting the network.
190        4. Ensure that the device connects to the network.
191        5. Release the network request.
192        6. Send another network specifier with the specific SSID & credentials
193           of WPA-PSK 2G network.
194        7. Ensure we disconnect from the previous network.
195        8. Wait for platform to scan and find matching networks.
196        9. Simulate user selecting the new network.
197        10. Ensure that the device connects to the new network.
198        """
199        # Complete flow for the first request.
200        wutils.wifi_connect_using_network_request(self.dut, self.wpa_psk_2g,
201                                                  self.wpa_psk_2g)
202        # Release the request.
203        self.dut.droid.wifiReleaseNetwork(self.wpa_psk_2g)
204        # Ensure we disconnected from the previous network.
205        wutils.wait_for_disconnect(self.dut)
206        self.dut.log.info("Disconnected from network %s", self.wpa_psk_2g)
207        self.dut.ed.clear_all_events()
208        # Complete flow for the second request.
209        wutils.wifi_connect_using_network_request(self.dut, self.open_5g,
210                                                  self.open_5g)
211
212    @test_tracker_info(uuid="f28b5dc9-771f-42ef-8178-e55e9a16f5c7")
213    def test_connect_to_wpa_psk_5g_while_connecting_to_open_2g(self):
214        """
215        Initiates a connection to network via network request with specific SSID
216
217        Steps:
218        1. Send a network specifier with the specific SSID & credentials of
219           WPA-PSK 5G network.
220        2. Send another network specifier with the specific SSID of Open 2G
221           network.
222        3. Ensure we disconnect from the previous network.
223        4. Wait for platform to scan and find matching networks.
224        5. Simulate user selecting the new network.
225        6. Ensure that the device connects to the new network.
226        """
227        # Make the first request.
228        self.dut.droid.wifiRequestNetworkWithSpecifier(self.open_2g)
229        self.dut.log.info("Sent network request with %s", self.open_2g)
230        # Complete flow for the second request.
231        wutils.wifi_connect_using_network_request(self.dut, self.wpa_psk_5g,
232                                                  self.wpa_psk_5g)
233
234    @test_tracker_info(uuid="2ab82a98-37da-4b27-abb6-578bedebccdc")
235    def test_connect_to_open_5g_while_connected_to_wpa_psk_2g(self):
236        """
237        Initiates a connection to network via network request with specific SSID
238
239        Steps:
240        1. Send a network specifier with the specific SSID of Open 5G network.
241        2. Wait for platform to scan and find matching networks.
242        3. Simulate user selecting the network.
243        4. Ensure that the device connects to the network.
244        5. Send another network specifier with the specific SSID & credentials
245           of WPA-PSK 2G network.
246        6. Ensure we disconnect from the previous network.
247        7. Wait for platform to scan and find matching networks.
248        8. Simulate user selecting the new network.
249        9. Ensure that the device connects to the new network.
250        """
251        # Complete flow for the first request.
252        wutils.wifi_connect_using_network_request(self.dut, self.wpa_psk_2g,
253                                                  self.wpa_psk_2g)
254        # Send the second request.
255        self.dut.droid.wifiRequestNetworkWithSpecifier(self.open_5g)
256        self.dut.log.info("Sent network request with %s", self.open_5g)
257        # Ensure we do not disconnect from the previous network until the user
258        # approves the new request.
259        self.dut.ed.clear_all_events()
260        wutils.ensure_no_disconnect(self.dut)
261
262        # Now complete the flow and ensure we connected to second request.
263        wutils.wait_for_wifi_connect_after_network_request(self.dut,
264                                                           self.open_5g)
265
266    @test_tracker_info(uuid="f0bb2213-b3d1-4fb8-bbdc-ad55c4fb05ed")
267    def test_connect_to_wpa_psk_2g_which_is_already_approved(self):
268        """
269        Initiates a connection to network via network request with specific SSID
270        bypassing user approval.
271
272        Steps:
273        1. Send a network specifier with the specific SSID/credentials of
274           WPA-PSK 2G network.
275        2. Wait for platform to scan and find matching networks.
276        3. Simulate user selecting the network.
277        4. Ensure that the device connects to the network.
278        5. Ensure we disconnect from the previous network.
279        6. Send another network specifier with the specific
280           SSID/BSSID/credentials of WPA-PSK 2G network.
281        7. Ensure that the device bypasses user approval & connects to the
282           same network.
283        """
284        # Complete flow for the first request.
285        wutils.wifi_connect_using_network_request(self.dut, self.wpa_psk_2g,
286                                                  self.wpa_psk_2g)
287        # Release the request.
288        self.dut.droid.wifiReleaseNetwork(self.wpa_psk_2g)
289        # Ensure we disconnected from the network.
290        wutils.wait_for_disconnect(self.dut)
291        self.dut.log.info("Disconnected from network %s", self.wpa_psk_2g)
292        self.dut.ed.clear_all_events()
293
294        # Find bssid for the WPA-PSK 2G network.
295        scan_results = self.dut.droid.wifiGetScanResults()
296        match_results = wutils.match_networks(
297            {WifiEnums.SSID_KEY: self.wpa_psk_2g[WifiEnums.SSID_KEY]},
298            scan_results)
299        asserts.assert_equal(len(match_results), 1,
300                             "Cannot find bssid for WPA-PSK 2G network")
301        bssid = match_results[0][WifiEnums.BSSID_KEY]
302        # Send the second request with bssid.
303        network_specifier_with_bssid = self.wpa_psk_2g.copy();
304        network_specifier_with_bssid[WifiEnums.BSSID_KEY] = bssid
305        self.dut.droid.wifiRequestNetworkWithSpecifier(
306            network_specifier_with_bssid)
307        self.dut.log.info("Sent network request with %r",
308                          network_specifier_with_bssid)
309
310        # Ensure we connected to second request without user approval.
311        wutils.wait_for_connect(self.dut, self.wpa_psk_2g[WifiEnums.SSID_KEY])
312
313    @test_tracker_info(uuid="fcf84d94-5f6e-4bd6-9f76-40a0228d4ebe")
314    def test_connect_to_wpa_psk_2g_which_is_already_approved_but_then_forgot(self):
315        """
316        Initiates a connection to network via network request with specific SSID
317        with user approval.
318
319        Steps:
320        1. Send a network specifier with the specific SSID/credentials of
321           WPA-PSK 2G network.
322        2. Wait for platform to scan and find matching networks.
323        3. Simulate user selecting the network.
324        4. Ensure that the device connects to the network.
325        4. Simulate user fogetting the network from the UI.
326        6. Ensure we disconnect from the previous network.
327        7. Send another network specifier with the specific
328           SSID/BSSID/credentials of WPA-PSK 2G network.
329        8. Ensure that the device does not bypass user approval & connects to the
330           same network with user approval. (This should also clear the blacklist)
331        9. Send the same network specifier with the specific
332           SSID/BSSID/credentials of WPA-PSK 2G network.
333        10.Ensure that the device bypasses user approval now & connects to the
334           same network.
335        """
336        # Complete flow for the first request.
337        wutils.wifi_connect_using_network_request(self.dut, self.wpa_psk_2g,
338                                                  self.wpa_psk_2g)
339
340        # Simulate user forgeting the ephemeral network.
341        self.dut.droid.wifiUserDisconnectNetwork(self.wpa_psk_2g[WifiEnums.SSID_KEY])
342        # Ensure we disconnected from the network.
343        wutils.wait_for_disconnect(self.dut)
344        self.dut.log.info("Disconnected from network %s", self.wpa_psk_2g)
345        self.dut.ed.clear_all_events()
346        # Release the first request.
347        self.dut.droid.wifiReleaseNetwork(self.wpa_psk_2g)
348
349        # Find bssid for the WPA-PSK 2G network.
350        scan_results = self.dut.droid.wifiGetScanResults()
351        match_results = wutils.match_networks(
352            {WifiEnums.SSID_KEY: self.wpa_psk_2g[WifiEnums.SSID_KEY]},
353            scan_results)
354        asserts.assert_equal(len(match_results), 1,
355                             "Cannot find bssid for WPA-PSK 2G network")
356        bssid = match_results[0][WifiEnums.BSSID_KEY]
357        # Send the second request with bssid.
358        network_specifier_with_bssid = self.wpa_psk_2g.copy();
359        network_specifier_with_bssid[WifiEnums.BSSID_KEY] = bssid
360        self.dut.droid.wifiRequestNetworkWithSpecifier(
361            network_specifier_with_bssid)
362        self.dut.log.info("Sent network request with %r",
363                          network_specifier_with_bssid)
364
365        # Ensure that we did not connect bypassing user approval.
366        assert_msg = "Device should not connect without user approval"
367        asserts.assert_false(
368            wutils.wait_for_connect(self.dut,
369                                    self.wpa_psk_2g[WifiEnums.SSID_KEY],
370                                    assert_on_fail=False),
371            assert_msg)
372
373        # Now complete the flow and ensure we connected to second request.
374        wutils.wait_for_wifi_connect_after_network_request(self.dut,
375                                                           self.wpa_psk_2g)
376
377        # Now make the same request again & ensure that we connect without user
378        # approval.
379        self.dut.droid.wifiRequestNetworkWithSpecifier(
380            network_specifier_with_bssid)
381        self.dut.log.info("Sent network request with %r",
382                          network_specifier_with_bssid)
383        wutils.wait_for_connect(self.dut, self.wpa_psk_2g[WifiEnums.SSID_KEY])
384
385    @test_tracker_info(uuid="2f90a266-f04d-4932-bb5b-d075bedfd56d")
386    def test_match_failure_with_invalid_ssid_pattern(self):
387        """
388        Initiates a connection to network via network request with SSID pattern
389        that does not match any networks.
390
391        Steps:
392        1. Trigger a connect to one of the networks (as a saved network).
393        2. Send a network specifier with the non-matching SSID pattern.
394        3. Ensure that the platform does not return any matching networks.
395        4. Wait for the request to timeout.
396        """
397        network = self.wpa_psk_5g
398
399        # Trigger a connection to a network as a saved network before the
400        # request and ensure that this does not change the behavior.
401        wutils.connect_to_wifi_network(self.dut, network, check_connectivity=False)
402
403        network_specifier = self.wpa_psk_5g.copy();
404        # Remove ssid & replace with invalid ssid pattern.
405        network_ssid = network_specifier.pop(WifiEnums.SSID_KEY)
406        network_specifier[WifiEnums.SSID_PATTERN_KEY] = \
407            network_ssid + "blah" + ".*"
408
409        self.dut.droid.wifiStartTrackingStateChange()
410        expected_ssid = network[WifiEnums.SSID_KEY]
411
412        self.dut.droid.wifiRequestNetworkWithSpecifierWithTimeout(
413              network_specifier, NETWORK_REQUEST_TIMEOUT_MS)
414        self.dut.log.info("Sent network request with invalid specifier %s",
415                    network_specifier)
416        time.sleep(wifi_constants.NETWORK_REQUEST_CB_REGISTER_DELAY_SEC)
417        self.dut.droid.wifiRegisterNetworkRequestMatchCallback()
418        # Wait for the request to timeout.
419        timeout_secs = NETWORK_REQUEST_TIMEOUT_MS * 2 / 1000
420        try:
421            on_unavailable_event = self.dut.ed.pop_event(
422                wifi_constants.WIFI_NETWORK_CB_ON_UNAVAILABLE, timeout_secs)
423            asserts.assert_true(on_unavailable_event, "Network request did not timeout")
424        except queue.Empty:
425            asserts.fail("No events returned")
426        finally:
427            self.dut.droid.wifiStopTrackingStateChange()
428
429    @test_tracker_info(uuid="caa96f57-840e-4997-9280-655edd3b76ee")
430    def test_connect_failure_user_rejected(self):
431        """
432        Initiates a connection to network via network request with specific SSID
433        which the user rejected.
434
435        Steps:
436        1. Send a network specifier with the specific SSID/credentials of
437           WPA-PSK 5G network.
438        2. Wait for platform to scan and find matching networks.
439        3. Simulate user rejecting the network.
440        4. Ensure that we get an instant onUnavailable callback.
441        5. Simulate user fogetting the network from the UI.
442        """
443        network = self.wpa_psk_5g
444        expected_ssid = network[WifiEnums.SSID_KEY]
445
446        self.dut.droid.wifiStartTrackingStateChange()
447
448        self.dut.droid.wifiRequestNetworkWithSpecifierWithTimeout(
449              network, NETWORK_REQUEST_TIMEOUT_MS)
450        self.dut.log.info("Sent network request with specifier %s", network)
451        time.sleep(wifi_constants.NETWORK_REQUEST_CB_REGISTER_DELAY_SEC)
452        self.dut.droid.wifiRegisterNetworkRequestMatchCallback()
453
454        # Wait for the platform to scan and return a list of networks
455        # matching the request
456        try:
457            matched_network = None
458            for _ in [0,  3]:
459                on_match_event = self.dut.ed.pop_event(
460                    wifi_constants.WIFI_NETWORK_REQUEST_MATCH_CB_ON_MATCH, 30)
461                asserts.assert_true(on_match_event,
462                                    "Network request on match not received.")
463                matched_scan_results = on_match_event["data"]
464                self.dut.log.debug("Network request on match results %s",
465                                   matched_scan_results)
466                matched_network = wutils.match_networks(
467                    {WifiEnums.SSID_KEY: network[WifiEnums.SSID_KEY]},
468                     matched_scan_results)
469                if matched_network:
470                    break;
471            asserts.assert_true(
472                matched_network, "Target network %s not found" % network)
473
474            # Send user rejection.
475            self.dut.droid.wifiSendUserRejectionForNetworkRequestMatch()
476            self.dut.log.info("Sent user rejection for network request %s",
477                              expected_ssid)
478
479            # Wait for the platform to raise unavailable callback
480            # instantaneously.
481            on_unavailable_event = self.dut.ed.pop_event(
482                wifi_constants.WIFI_NETWORK_CB_ON_UNAVAILABLE,
483                NETWORK_REQUEST_INSTANT_FAILURE_TIMEOUT_SEC)
484            asserts.assert_true(on_unavailable_event,
485                                "Network request on available not received.")
486        except queue.Empty:
487            asserts.fail("Expected events not returned")
488        finally:
489            self.dut.droid.wifiStopTrackingStateChange()
490