1#!/usr/bin/env python3
2#
3#   Copyright 2020 - 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"""
17A test that saves various networks and verifies the behavior of save, get, and
18remove through the ClientController API of WLAN policy.
19"""
20
21from acts import signals
22from acts.controllers.access_point import setup_ap
23from acts.controllers.ap_lib import hostapd_constants
24from acts.controllers.ap_lib import hostapd_security
25from acts_contrib.test_utils.wifi.WifiBaseTest import WifiBaseTest
26from acts.utils import rand_ascii_str, rand_hex_str
27
28PSK_LEN = 64
29TIME_WAIT_FOR_DISCONNECT = 30
30TIME_WAIT_FOR_CONNECT = 30
31
32STATE_CONNECTED = "Connected"
33STATE_CONNECTING = "Connecting"
34CONNECTIONS_ENABLED = "ConnectionsEnabled"
35CONNECTIONS_DISABLED = "ConnectionsDisabled"
36SECURITY_NONE = "none"
37WEP = "wep"
38WPA = "wpa"
39WPA2 = "wpa2"
40WPA3 = "wpa3"
41CREDENTIAL_TYPE_NONE = "none"
42PASSWORD = "password"
43PSK = "psk"
44CREDENTIAL_VALUE_NONE = ""
45
46
47class SavedNetworksTest(WifiBaseTest):
48    """WLAN policy commands test class.
49
50    Test Bed Requirement:
51    * One or more Fuchsia devices
52    * One Access Point
53    """
54
55    def setup_class(self):
56        super().setup_class()
57        # Keep track of whether we have started an access point in a test
58        if len(self.fuchsia_devices) < 1:
59            raise EnvironmentError("No Fuchsia devices found.")
60        for fd in self.fuchsia_devices:
61            fd.configure_wlan(association_mechanism='policy',
62                              preserve_saved_networks=True)
63
64    def setup_test(self):
65        for fd in self.fuchsia_devices:
66            if not fd.wlan_policy_controller.remove_all_networks():
67                raise EnvironmentError(
68                    "Failed to remove all networks in setup")
69        self.access_points[0].stop_all_aps()
70
71    def teardown_class(self):
72        for fd in self.fuchsia_devices:
73            fd.wlan_policy_controller.remove_all_networks()
74        self.access_points[0].stop_all_aps()
75
76    def save_bad_network(self, fd, ssid, security_type, password=""):
77        """ Saves a network as specified on the given device and verify that we
78        Args:
79            fd: The Fuchsia device to save the network on
80            ssid: The SSID or name of the network to save.
81            security_type: The security type to save the network as, ie "none",
82                        "wep", "wpa", "wpa2", or "wpa3"
83            password: The password to save for the network. Empty string represents
84                    no password, and PSK should be provided as 64 character hex string.
85        """
86        if fd.wlan_policy_controller.save_network(ssid,
87                                                  security_type,
88                                                  password=password):
89            self.log.info(
90                "Attempting to save bad network config %s did not give an error"
91                % ssid)
92            raise signals.TestFailure("Failed to get error saving bad network")
93
94    def check_get_saved_network(self, fd, ssid, security_type, credential_type,
95                                credential_value):
96        """ Verify that get saved networks sees the single specified network. Used
97            for the tests that save and get a single network. Maps security types of
98            expected and actual to be case insensitive.
99        Args:
100            fd: Fuchsia device to run on.
101            ssid: The name of the network to check for.
102            security_type: The security of the network, ie "none", "wep", "wpa",
103                        "wpa2", or "wpa3".
104            credential_type: The type of credential saved for the network, ie
105                            "none", "password", or "psk".
106            credential_value: The actual credential, or "" if there is no credential.
107        """
108        expected_networks = [{
109            "ssid": ssid,
110            "security_type": security_type,
111            "credential_type": credential_type,
112            "credential_value": credential_value
113        }]
114        self.check_saved_networks(fd, expected_networks)
115
116    def check_saved_networks(self, fd, expected_networks):
117        """ Verify that the saved networks we get from the device match the provided
118            list of networks.
119        Args:
120            fd: The Fuchsia device to run on.
121            expected_networks: The list of networks we expect to get from the device,
122                            unordered and in the same format as we would get:
123                            [{"credential_type": _, "credential_value": _,
124                            "security_type": _, "ssid": _}, ...] There should be
125                            no duplicates in expected networks.
126        """
127        actual_networks = list(
128            map(self.lower_case_network,
129                fd.wlan_policy_controller.get_saved_networks()))
130        expected_networks = list(
131            map(self.lower_case_network,
132                fd.wlan_policy_controller.get_saved_networks()))
133
134        if len(actual_networks) != len(expected_networks):
135            self.log.info(
136                "Number of expected saved networks does not match the actual number."
137                "Expected: %d, actual: %d" %
138                (len(actual_networks), len(expected_networks)))
139            raise signals.TestFailure(
140                "Failed to get the expected number of saved networks")
141        for network in actual_networks:
142            if network not in expected_networks:
143                self.log.info(
144                    "Actual and expected networks do not match. Actual: %s,\n"
145                    "Expected: %s" % (actual_networks, expected_networks))
146                raise signals.TestFailure("Got an unexpected saved network")
147
148    def lower_case_network(self, network):
149        if "security_type" not in network:
150            self.log.error("Missing security type in network %s" % network)
151            raise signals.TestFailure("Network is missing security type")
152        if "credential_type" not in network:
153            self.log.error("Missing credential type in network %s" % network)
154            raise signals.TestFailure("Network is missing credential type")
155        {"ssid": network["ssid"], "security_type": network["security_type"]}
156
157    def save_and_check_network(self, ssid, security_type, password=""):
158        """ Perform a test for saving, getting, and removing a single network on each
159            device.
160        Args:
161            ssid: The network name to use.
162            security_type: The security of the network as a string, ie "none",
163                        "wep", "wpa", "wpa2", or "wpa3" (case insensitive)
164            password: The password of the network. PSK should be given as 64
165                    hexadecimal characters and none should be an empty string.
166        """
167        for fd in self.fuchsia_devices:
168            if not fd.wlan_policy_controller.save_network(
169                    ssid, security_type, password=password):
170                raise signals.TestFailure("Failed to save network")
171            self.check_get_saved_network(fd, ssid, security_type,
172                                         self.credentialType(password),
173                                         password)
174
175    def start_ap(self, ssid, security_type, password=None, hidden=False):
176        """ Starts an access point.
177        Args:
178            ssid: the SSID of the network to broadcast
179            security_type: the security type of the network to be broadcasted. This can be
180                None, "wep" "wpa", "wpa2", or "wpa3" (or from hostapd_constants.py)
181            password: the password to connect to the broadcasted network. The password is ignored
182                if security type is none.
183        """
184        # Put together the security configuration of the network to be
185        # broadcasted. Open networks are represented by no security.
186        if security_type == None or security_type.upper() == SECURITY_NONE:
187            security = None
188        else:
189            security = hostapd_security.Security(security_mode=security_type,
190                                                 password=password)
191
192        if len(self.access_points) > 0:
193            # Create an AP with default values other than the specified values.
194            setup_ap(self.access_points[0],
195                     'whirlwind',
196                     hostapd_constants.AP_DEFAULT_CHANNEL_5G,
197                     ssid,
198                     security=security)
199
200        else:
201            self.log.error(
202                "No access point available for test, please check config")
203            raise EnvironmentError("Failed to set up AP for test")
204
205    def credentialType(self, credentialValue):
206        """ Returns the type of the credential to compare against values reported """
207        if len(credentialValue) == PSK_LEN:
208            return PSK
209        elif len(credentialValue) == 0:
210            return "none"
211        else:
212            return PASSWORD
213
214    def same_network_identifier(self, net_id, ssid, security_type):
215        """ Returns true if the network id is made of the given ssid and security
216            type, and false otherwise. Security type check is case insensitive.
217        """
218        return net_id["ssid"] == ssid and net_id["type_"].upper(
219        ) == security_type.upper()
220
221    """Tests"""
222
223    def test_open_network_with_password(self):
224        for fd in self.fuchsia_devices:
225            # Save an open network with a password and verify that it fails to
226            # save.
227            self.save_bad_network(fd, rand_ascii_str(10), SECURITY_NONE,
228                                  rand_ascii_str(8))
229            self.check_saved_networks(fd, {})
230
231    def test_open_network(self):
232        ssid = rand_ascii_str(10)
233        self.save_and_check_network(ssid, SECURITY_NONE)
234
235    def test_network_with_psk(self):
236        ssid = rand_ascii_str(11)
237        # PSK are translated from hex to bytes when saved, and when returned
238        # by get_saved_networks it will be lower case.
239        psk = rand_hex_str(PSK_LEN).lower()
240        self.save_and_check_network(ssid, WPA2, psk)
241
242    def test_wep_network(self):
243        ssid = rand_ascii_str(12)
244        password = rand_ascii_str(13)
245        self.save_and_check_network(ssid, WEP, password)
246
247    def test_wpa2_network(self):
248        ssid = rand_ascii_str(9)
249        password = rand_ascii_str(15)
250        self.save_and_check_network(ssid, WPA2, password)
251
252    def test_wpa_network(self):
253        ssid = rand_ascii_str(16)
254        password = rand_ascii_str(9)
255        self.save_and_check_network(ssid, WPA, password)
256
257    def test_wpa3_network(self):
258        ssid = rand_ascii_str(9)
259        password = rand_ascii_str(15)
260        self.save_and_check_network(ssid, WPA3, password)
261
262    def test_save_network_persists(self):
263        ssid = rand_ascii_str(10)
264        security = WPA2
265        password = rand_ascii_str(10)
266        for fd in self.fuchsia_devices:
267            if not fd.wlan_policy_controller.save_network(
268                    ssid, security, password=password):
269                raise signals.TestFailure("Failed to save network")
270            # Reboot the device. The network should be persistently saved
271            # before the command is completed.
272            fd.reboot()
273            self.check_get_saved_network(fd, ssid, security, PASSWORD,
274                                         password)
275
276    def test_same_ssid_diff_security(self):
277        for fd in self.fuchsia_devices:
278            saved_networks = fd.wlan_policy_controller.get_saved_networks()
279            ssid = rand_ascii_str(19)
280            password = rand_ascii_str(12)
281            if not fd.wlan_policy_controller.save_network(
282                    ssid, WPA2, password=password):
283                raise signals.TestFailure("Failed to save network")
284            saved_networks.append({
285                "ssid": ssid,
286                "security_type": WPA2,
287                "credential_type": PASSWORD,
288                "credential_value": password
289            })
290            if not fd.wlan_policy_controller.save_network(ssid, SECURITY_NONE):
291                raise signals.TestFailure("Failed to save network")
292            saved_networks.append({
293                "ssid": ssid,
294                "security_type": SECURITY_NONE,
295                "credential_type": CREDENTIAL_TYPE_NONE,
296                "credential_value": CREDENTIAL_VALUE_NONE
297            })
298            actual_networks = fd.wlan_policy_controller.get_saved_networks()
299            # Both should be saved and present in network store since the have
300            # different security types and therefore different network identifiers.
301            self.check_saved_networks(fd, actual_networks)
302
303    def test_remove_disconnects(self):
304        # If we save, connect to, then remove the network while still connected
305        # to it, we expect the network will disconnect. This test requires a
306        # wpa2 network in the test config. Remove all other networks first so
307        # that we can't auto connect to them
308        ssid = rand_ascii_str(10)
309        security = WPA2
310        password = rand_ascii_str(10)
311        self.start_ap(ssid, security, password)
312
313        for fd in self.fuchsia_devices:
314            fd.wlan_policy_controller.wait_for_no_connections()
315
316            if not fd.wlan_policy_controller.save_and_connect:
317                raise signals.TestFailure(
318                    "Failed to saved and connect to network")
319
320            if not fd.wlan_policy_controller.remove_all_networks_and_wait_for_no_connections(
321            ):
322                raise signals.TestFailure(
323                    "Failed to disconnect from removed network")
324
325    def test_auto_connect_open(self):
326        # Start up AP with an open network with a random SSID
327        ssid = rand_ascii_str(10)
328        self.start_ap(ssid, None)
329        for fd in self.fuchsia_devices:
330            fd.wlan_policy_controller.wait_for_no_connections()
331
332            # Save the network and make sure that we see the device auto connect to it.
333            security = SECURITY_NONE
334            password = CREDENTIAL_VALUE_NONE
335            if not fd.wlan_policy_controller.save_network(
336                    ssid, security, password=password):
337                raise signals.TestFailure("Failed to save network")
338            if not fd.wlan_policy_controller.wait_for_connect(
339                    ssid, security, timeout=TIME_WAIT_FOR_CONNECT):
340                raise signals.TestFailure("Failed to connect to network")
341
342    def test_auto_connect_wpa3(self):
343        # Start up AP with an open network with a random SSID
344        ssid = rand_ascii_str(10)
345        security = WPA3
346        password = rand_ascii_str(10)
347        self.start_ap(ssid, security, password)
348        for fd in self.fuchsia_devices:
349            fd.wlan_policy_controller.wait_for_no_connections()
350
351            # Save the network and make sure that we see the device auto connect to it.
352            if not fd.wlan_policy_controller.save_network(
353                    ssid, security, password=password):
354                raise signals.TestFailure("Failed to save network")
355            if not fd.wlan_policy_controller.wait_for_connect(
356                    ssid, security, timeout=TIME_WAIT_FOR_CONNECT):
357                raise signals.TestFailure("Failed to connect to network")
358